/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package rdp.restart; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.RunnableScheduledFuture; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** * * @author Bob */ public final class Restart implements Runnable, sage.SageTVPlugin { private static final LogLevel OFF = new LogLevel("OFF", Integer.MAX_VALUE); private static final LogLevel SEVERE = new LogLevel("SEVERE", 1000); private static final LogLevel WARNING = new LogLevel("WARNING", 900); private static final LogLevel INFO = new LogLevel("INFO", 800); private static final LogLevel CONFIG = new LogLevel("CONFIG", 700); private static final LogLevel FINE = new LogLevel("FINE", 500); private static final LogLevel FINER = new LogLevel("FINER", 400); private static final LogLevel FINEST = new LogLevel("FINEST", 300); private static final LogLevel ALL = new LogLevel("ALL", Integer.MIN_VALUE); private static final String LOCAL_CONTEXT = "SAGETV_PROCESS_LOCAL_UI"; private static final SimpleDateFormat SDF = new SimpleDateFormat(); private static final DecimalFormat DEC_FMT = new DecimalFormat(); private static final Date DATE = new Date(); private static final long SINGLE_SECOND = 1000L; private static final long SINGLE_MINUTE = 60 * SINGLE_SECOND; private static final long SINGLE_HOUR = 60 * SINGLE_MINUTE; private static final long SINGLE_DAY = 24L * SINGLE_HOUR; private static final long SINGLE_WEEK = 7L * SINGLE_DAY; private static final long SINGLE_YEAR = 365L * SINGLE_DAY + 6L * SINGLE_HOUR + 9L * SINGLE_MINUTE + 9L * SINGLE_SECOND; private static final long SINGLE_MONTH = SINGLE_YEAR / 12L; private static final TimeUnit MILLISECONDS = TimeUnit.MILLISECONDS; private static final String PREFIX = ">>>>rdp_restart"; private static final String BRANCH = "rdp_restart/"; private static final Pattern DURATION = Pattern.compile( "(\\bS{1,3}\\b)|(\\bs{1,2}\\b)|(\\bm{1,2}\\b)|(\\bH{1,2}\\b)|" + "(\\bD{1,3}\\b)|(\\bd{1,2}\\b)|(\\bw{1,2}\\b)|(\\bW\\b)|" + "(\\bM{1,2}\\b)|(\\by{1,4}\\b)"); private static Restart startup; private static LogLevel level; private static BusyScheduledThreadPool exec; private static class LogLevel implements Comparable { private static ArrayList known = new ArrayList(); final String desc; final int value; LogLevel(String desc, int value) { this.desc = desc; this.value = value; synchronized (LogLevel.class) { known.add(this); } } public int compareTo(LogLevel level) { int thisVal = this.value; int anotherVal = level.value; return (thisVal > anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1)); } public static LogLevel parseLevel(String desc) { // Look for a known Level with the given non-localized name. for (int i = 0; i < known.size(); i++) { LogLevel l = known.get(i); if (desc.equalsIgnoreCase(l.desc)) { return l; } } // Now, check if the given name is an integer. If so, // first look for a Level with the given value and then // if necessary create one. try { int x = Integer.parseInt(desc); for (int i = 0; i < known.size(); i++) { LogLevel l = known.get(i); if (l.value == x) { return l; } } // Create a new Level. LogLevel result = new LogLevel(desc, x); known.add(result); return result; } catch (NumberFormatException ex) { // Not an integer. // Drop through. } // Finally, look for a known level with the given localized name, // in the current default locale. // This is relatively expensive, but not excessively so. for (int i = 0; i < known.size(); i++) { LogLevel l = known.get(i); if (desc.equalsIgnoreCase(l.desc)) { return l; } } // OK, we've tried everything and failed throw new IllegalArgumentException("Bad level \"" + desc + "\""); } public static String toString(LogLevel level) { return level.desc; } } private static class BusyScheduledThreadPool extends ScheduledThreadPoolExecutor { BusyScheduledThreadPool(int maxCoreThreads) { super(maxCoreThreads); } @Override protected RunnableScheduledFuture decorateTask( Runnable runnable, RunnableScheduledFuture task) { return super.decorateTask(runnable, task); } @Override protected RunnableScheduledFuture decorateTask( Callable callable, RunnableScheduledFuture task) { return super.decorateTask(callable, task); } @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); } @Override public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { level = parseLogLevel(getProperty("logging_level", "WARNING")); RescheduleFuture future = null; Date start = new Date(System.currentTimeMillis() + delay); RepeatingCallable callable = new RepeatingCallable( command, start, 0, unit); future = new RescheduleFuture(super.schedule( callable, delay, unit)); callable.setFuture(future); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " scheduled at " + formatDate("MM/dd-HH:mm:ss.SSS", start.getTime())); } return future; } @Override public ScheduledFuture schedule(Callable command, long delay, TimeUnit unit) { level = parseLogLevel(getProperty("logging_level", "WARNING")); RescheduleFuture future = null; Date start = new Date(System.currentTimeMillis() + delay); RepeatingCallable callable = new RepeatingCallable( command, start, 0, unit); future = new RescheduleFuture(super.schedule( callable, delay, unit)); callable.setFuture(future); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " scheduled at " + formatDate("MM/dd-HH:mm:ss.SSS", start.getTime())); } return future; } public ScheduledFuture schedule(Runnable command, Date start) { level = parseLogLevel(getProperty("logging_level", "WARNING")); RescheduleFuture future = null; RepeatingCallable callable = new RepeatingCallable( command, start, 0, MILLISECONDS); long delay = callable.getDelay(); future = new RescheduleFuture(super.schedule( callable, delay, MILLISECONDS)); callable.setFuture(future); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " scheduled at " + formatDate("MM/dd-HH:mm:ss.SSS", start.getTime())); } return future; } public ScheduledFuture schedule(Callable command, Date start) { level = parseLogLevel(getProperty("logging_level", "WARNING")); RescheduleFuture future = null; RepeatingCallable callable = new RepeatingCallable( command, start, 0, MILLISECONDS); long delay = callable.getDelay(); future = new RescheduleFuture(super.schedule( callable, delay, MILLISECONDS)); callable.setFuture(future); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " scheduled at " + formatDate("MM/dd-HH:mm:ss.SSS", start.getTime())); } return future; } @Override public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { level = parseLogLevel(getProperty("logging_level", "WARNING")); Date start = new Date(System.currentTimeMillis() + initialDelay); RescheduleFuture future = null; RepeatingCallable callable = new RepeatingCallable( command, start, unit.toMillis(period), MILLISECONDS); long delay = callable.getDelay(); future = new RescheduleFuture(super.schedule( callable, delay, MILLISECONDS)); callable.setFuture(future); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " scheduled at " + formatDate("MM/dd-HH:mm:ss.SSS", start.getTime()) + " repeating every " + formatDuration("dd-HH:mm:ss.SSS", callable.getPeriod())); } return future; } @Override public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { level = parseLogLevel(getProperty("logging_level", "WARNING")); Date start = new Date(System.currentTimeMillis() + initialDelay); RescheduleFuture future = null; RepeatingCallable callable = new RepeatingCallable( command, start, delay, MILLISECONDS); future = new RescheduleFuture(super.schedule( callable, initialDelay, MILLISECONDS)); callable.setFuture(future); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " scheduled at " + formatDate("MM/dd-HH:mm:ss.SSS", start.getTime()) + " repeating every " + formatDuration("dd-HH:mm:ss.SSS", callable.getPeriod())); } return future; } public ScheduledFuture scheduleAtFixedRate( Callable command, long initialDelay, long period, TimeUnit unit) { level = parseLogLevel(getProperty("logging_level", "WARNING")); Date start = new Date(System.currentTimeMillis() + initialDelay); RescheduleFuture future = null; RepeatingCallable callable = new RepeatingCallable( command, start, period, MILLISECONDS); long delay = callable.getDelay(); future = new RescheduleFuture(super.schedule( callable, delay, MILLISECONDS)); callable.setFuture(future); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " scheduled at " + formatDate("MM/dd-HH:mm:ss.SSS", start.getTime()) + " repeating every " + formatDuration("dd-HH:mm:ss.SSS", callable.getPeriod())); } return future; } public ScheduledFuture scheduleWithFixedDelay( Callable command, long initialDelay, long delay, TimeUnit unit) { level = parseLogLevel(getProperty("logging_level", "WARNING")); Date start = new Date(System.currentTimeMillis() + initialDelay); RescheduleFuture future = null; RepeatingCallable callable = new RepeatingCallable( command, start, delay, MILLISECONDS); future = new RescheduleFuture(super.schedule( callable, initialDelay, MILLISECONDS)); callable.setFuture(future); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " scheduled at " + formatDate("MM/dd-HH:mm:ss.SSS", start.getTime()) + " repeating every " + formatDuration("dd-HH:mm:ss.SSS", callable.getPeriod())); } return future; } public ScheduledFuture scheduleAtFixedRate( Callable command, Date start, long period, TimeUnit unit) { level = parseLogLevel(getProperty("logging_level", "WARNING")); RescheduleFuture future = null; RepeatingCallable callable = new RepeatingCallable( command, start, period, MILLISECONDS); long current = System.currentTimeMillis(); long initialDelay = start.getTime() - current <= 0 ? 0 : start.getTime() - current; future = new RescheduleFuture(super.schedule( callable, initialDelay, MILLISECONDS)); callable.setFuture(future); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " scheduled at " + formatDate("MM/dd-HH:mm:ss.SSS", start.getTime()) + " repeating every " + formatDuration("dd-HH:mm:ss.SSS", callable.getPeriod())); } return future; } public ScheduledFuture scheduleWithFixedDelay( Callable command, Date start, long delay, TimeUnit unit) { level = parseLogLevel(getProperty("logging_level", "WARNING")); RescheduleFuture future = null; RepeatingCallable callable = new RepeatingCallable( command, start, delay, MILLISECONDS); long current = System.currentTimeMillis(); long initialDelay = start.getTime() - current <= 0 ? 0 : start.getTime() - current; future = new RescheduleFuture(super.schedule( callable, initialDelay, MILLISECONDS)); callable.setFuture(future); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " scheduled at " + formatDate("MM/dd-HH:mm:ss.SSS", start.getTime()) + " repeating every " + formatDuration("dd-HH:mm:ss.SSS", callable.getPeriod())); } return future; } } private static class RepeatingCallable implements Callable { private long period, start; private Callable callable; private RescheduleFuture future; RepeatingCallable(Callable callable, Date start, long period, TimeUnit unit) { this.callable = callable; this.start = start.getTime(); this.period = unit.toMillis(period); } RepeatingCallable(Runnable runnable, V result, Date start, long period, TimeUnit unit) { callable = Executors.callable(runnable, result); this.start = start.getTime(); this.period = unit.toMillis(period); } RepeatingCallable(Runnable runnable, Date start, long period, TimeUnit unit) { this(runnable, null, start, period, unit); } public V call() throws Exception { level = parseLogLevel(getProperty("logging_level", "WARNING")); V result = null; try { result = callable.call(); } catch (RescheduleException ex) { long retry = parseLong(getProperty("minutes_until_retry", "15")) * SINGLE_MINUTE; future.setFuture(exec.schedule(callable, retry, MILLISECONDS)); if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " System was busy rescheduling"); } return null; } if (period > 0) { long current = System.currentTimeMillis(); long delay = start; while (delay < current) { delay += period; } delay -= current; future.setFuture(exec.schedule(callable, delay, MILLISECONDS)); if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " rescheduling after reboot issues"); } } return result; } public long getPeriod() { return period; } public long getDelay() { return start - System.currentTimeMillis(); } public ScheduledFuture getFuture() { return future; } void setFuture(RescheduleFuture future) { this.future = future; } } private static class RescheduleFuture implements ScheduledFuture { private ScheduledFuture future; RescheduleFuture(ScheduledFuture future) { this.future = future; } public long getDelay(TimeUnit unit) { return future.getDelay(unit); } public int compareTo(Delayed o) { return future.compareTo(o); } public boolean cancel(boolean mayInterruptIfRunning) { return future.cancel(mayInterruptIfRunning); } public boolean isCancelled() { return future.isCancelled(); } public boolean isDone() { return future.isDone(); } public V get() throws InterruptedException, ExecutionException { return future.get(); } public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return future.get(timeout, unit); } void setFuture(ScheduledFuture future) { this.future = future; } } private static class RescheduleException extends Exception { private static final long serialVersionUID = 1L; RescheduleException() { super(); } RescheduleException(String msg) { super(msg); } RescheduleException(String msg, Throwable t) { super(msg, t); } RescheduleException(Throwable t) { super(t); } } private static class StreamGobbler implements Runnable { private transient InputStream is; private String streamType; StreamGobbler(InputStream is, String streamType) { level = parseLogLevel(getProperty("logging_level", "WARNING")); this.is = is; this.streamType = streamType; if ((level != null) && (level.compareTo(FINER) >= 0)) { System.out.println(PREFIX + " " + streamType); } } public void run() { level = parseLogLevel(getProperty("logging_level", "WARNING")); try { BufferedReader br = new BufferedReader(new InputStreamReader(is)); String l; if ((level != null) && (level.compareTo(FINER) >= 0)) { System.out.println(PREFIX + " " + streamType + " - Now reading output"); } while ((l = br.readLine()) != null) { if ((level != null) && (level.compareTo(FINER) >= 0)) { System.out.println(PREFIX + " " + streamType + l); } } } catch (IOException ex) { printStackTrace("IOException in StreamGobbler", PREFIX, ex); } } } private static class RestartProcess implements Callable { public Object call() throws Exception { level = parseLogLevel(getProperty("logging_level", "WARNING")); String command = getProperty("cmd", "shutdown.exe /r"); if (shouldPause()) { throw new RescheduleException("System busy"); } if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " executing " + command); } Process p = Runtime.getRuntime().exec(command, null, null); if ((level != null) && (level.compareTo(INFO) >= 0)) { new Thread(new StreamGobbler(p.getInputStream(), "Output>>")).start(); new Thread(new StreamGobbler(p.getErrorStream(), "Error>>>")).start(); } p.waitFor(); return null; } private boolean shouldPause() { level = parseLogLevel(getProperty("logging_level", "WARNING")); Object[] mediaFiles = getCurrentlyRecordingMediaFiles(); if (mediaFiles == null) { if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " current recordings:null"); } } else if (mediaFiles.length > 0) { if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " currently recording so pausing"); } return true; } else { if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " current recordings:0"); } } long start = System.currentTimeMillis(); long window = parseLong(getProperty("minutes_before_recording", "15")) * SINGLE_MINUTE; long end = start + window; Object[] recordings = getScheduledRecordingsForTime(start, end); if (recordings == null) { if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " in window recordings=null"); } } else if (recordings.length > 0) { if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " in window so pausing"); } return true; } else { if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " in window recordings=0"); } } if (hasMediaFile()) { if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " server playing back media so pausing"); } return true; } boolean allowClients = parseBoolean( getProperty("reboot_with_clients_connected", "false")); if (allowClients && clientsConnected()) { if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " client connected so pausing"); } return true; } boolean allowExtenders = parseBoolean( getProperty("reboot_with_extenders_connected", "false")); if (allowExtenders && extendersConnected()) { if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " extender connected so pausing"); } return true; } return false; } private boolean clientsConnected() { level = parseLogLevel(getProperty("logging_level", "WARNING")); String[] clients = getConnectedClients(); if (clients == null) { if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " connected clients:null"); } return false; } else if (clients.length > 0) { ArrayList clientsList = new ArrayList(Arrays.asList(clients)); for (Iterator it = clientsList.iterator(); it.hasNext();) { String client = it.next(); if (client.startsWith("/127.0.0.1")) { it.remove(); } } if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " connected clients:" + clientsList); } return true; } else { if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " connected clients:0"); } return false; } } private boolean extendersConnected() { level = parseLogLevel(getProperty("logging_level", "WARNING")); String[] extenders = getUIContextNames(); if (extenders == null) { if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " connected extenders/placeshifters:null"); } return false; } else if (extenders.length > 0) { if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " connected extenders/placeshifters:" + Arrays.asList(extenders)); } return true; } else { if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println(PREFIX + " connected clients:0"); } return false; } } } public static Restart getInstance() { return startup; } static long parseLong(String value) { return Long.parseLong(value); } static int parseInt(String value) { return Integer.parseInt(value); } static LogLevel parseLogLevel(String value) { return LogLevel.parseLevel(value); } static boolean parseBoolean(String value) { return Boolean.parseBoolean(value); } static String[] getSubpropertiesThatAreLeaves() { return (String[]) apiUI(null, "GetSubpropertiesThatAreLeaves", BRANCH); } static String getProperty(String propertyName, String defaultValue) { return (String) apiUI(null, "GetProperty", BRANCH + propertyName, defaultValue); } static void setProperty(String propertyName, String propertyValue) { apiUI(null, "SetProperty", BRANCH + propertyName, propertyValue); } static boolean hasMediaFile() { return ((Boolean) apiUI(LOCAL_CONTEXT, "HasMediaFile")).booleanValue(); } static String[] getUIContextNames() { return (String[]) apiUI(null, "GetUIContextNames"); } static String[] getConnectedClients() { return (String[]) apiUI(null, "GetConnectedClients"); } static Object[] getCurrentlyRecordingMediaFiles() { return (Object[]) apiUI(null, "GetCurrentlyRecordingMediaFiles"); } static Object[] getScheduledRecordingsForTime(long startTime, long endTime) { return (Object[]) apiUI(null, "GetScheduledRecordingsForTime", startTime, endTime); } static Object apiUI(String uiContext, String method, Object... args) { if ((uiContext == null) || (uiContext.isEmpty())) { uiContext = LOCAL_CONTEXT; } if ((level != null) && (level.compareTo(INFO) >= 0)) { String s = ""; if (args != null) { int i = 0; for (Object arg : args) { s += " [" + String.valueOf(i++) + "]:" + arg.toString(); } } System.out.println(PREFIX + " Calling Sage method:" + method + " with arguments:" + s); } try { return sage.SageTV.apiUI(uiContext, method, args); } catch (InvocationTargetException ex) { String s = ""; if (args != null) { int i = 0; for (Object arg : args) { s += " [" + String.valueOf(i++) + "]:" + arg.toString(); } } printStackTrace("InvocationTargetException calling method:" + method + " args:" + s, ">>>>rdp_restart", ex); return null; } } static String formatNumber(String pattern, double num) { DEC_FMT.applyPattern(pattern); return DEC_FMT.format(num); } static String formatNumber(String pattern, long num) { DEC_FMT.applyPattern(pattern); return DEC_FMT.format(num); } static String formatDate(String pattern, long date) { DATE.setTime(date); SDF.applyPattern(pattern); return SDF.format(DATE); } static String formatDuration(String pattern, long duration) { int years = (int) (duration / SINGLE_YEAR); duration %= SINGLE_YEAR; int daysInYear = (int) (duration / SINGLE_DAY); int weeksInYear = (int) (duration / SINGLE_WEEK); int months = (int) (duration / SINGLE_MONTH); int daysInMonth = (int) ((duration % SINGLE_MONTH) / SINGLE_DAY); int weeksInMonth = (int) ((duration % SINGLE_MONTH) / SINGLE_WEEK); duration %= SINGLE_DAY; int hours = (int) (duration / SINGLE_HOUR); duration %= SINGLE_HOUR; int minutes = (int) (duration / SINGLE_MINUTE); duration %= SINGLE_MINUTE; int seconds = (int) (duration / SINGLE_SECOND); int milliseconds = (int) (duration % SINGLE_SECOND); if ((level != null) && (level.compareTo(INFO) >= 0)) { System.out.println("Years:" + years + " Months:" + months + " WeeksInYear:" + weeksInYear + " WeeksInMonth:" + weeksInMonth + " DaysInYear:" + daysInYear + " DaysInMonth:" + daysInMonth + " Hours:" + hours + " Minutes:" + minutes + " Seconds:" + seconds + " Milliseconds:" + milliseconds); } StringBuffer sb = new StringBuffer(); try { Matcher regexMatcher = DURATION.matcher(pattern); int value = 0; String rpl; while (regexMatcher.find()) { for (int i = 1; i <= regexMatcher.groupCount(); i++) { if (regexMatcher.group(i) != null) { switch (i) { case 1: value = milliseconds; break; case 2: value = seconds; break; case 3: value = minutes; break; case 4: value = hours; break; case 5: value = daysInYear; break; case 6: value = daysInMonth; break; case 7: value = weeksInYear; break; case 8: value = weeksInMonth; break; case 9: value = months; break; case 10: value = years; break; } int len = regexMatcher.end(i) - regexMatcher.start(i); if (len == 1) { rpl = "####"; } else { rpl = "0000".substring(0, len); } regexMatcher.appendReplacement(sb, formatNumber(rpl, value)); } } } regexMatcher.appendTail(sb); } catch (PatternSyntaxException ex) { printStackTrace("PatternSyntaxException", PREFIX, ex); } String rtnval = sb.toString(); return rtnval; } public static void printStackTrace(String message, String prefix, Throwable ex) { StackTraceElement[] ste; if (ex instanceof InvocationTargetException) { Throwable tr = ex.getCause(); System.out.println(prefix + " " + message); System.out.println(prefix + " " + ex.getClass().getName()); System.out.println(prefix + " " + ex.getMessage()); ste = getRecursiveStackTrace(prefix, tr); } else { ste = ex.getStackTrace(); System.out.println(prefix + " " + message); System.out.println(prefix + " " + ex.getClass().getName()); System.out.println(prefix + " " + ex.getMessage()); } for (int i = 0; i < ste.length; i++) { System.out.println(prefix + " " + ste[i]); } } private static StackTraceElement[] getRecursiveStackTrace(String prefix, Throwable tr) { StackTraceElement[] result; if (tr.getCause() == null) { result = tr.getStackTrace(); } else { System.out.println(prefix + " " + tr.getClass().getName()); System.out.println(prefix + " " + tr.getMessage()); result = getRecursiveStackTrace(prefix, tr.getCause()); } return result; } public Restart(sage.SageTVPluginRegistry registry, boolean reset) { if (reset) { System.out.println(PREFIX + " resetting config in constructor"); resetConfig(); } } public void run() { start(); } // This method is called when the plugin should startup public void start() { startup = this; System.out.println(PREFIX + " starting Restart"); initConfig(); level = parseLogLevel(getProperty("logging_level", "WARNING")); System.out.println(PREFIX + " logging level " + (level == null ? null : level.desc)); exec = new BusyScheduledThreadPool(1); long frequency = parseLong(getProperty("frequency_HH", "24")) * SINGLE_HOUR; System.out.println(PREFIX + " frequency:" + frequency); String hhmm = getProperty("after_midnight_HHMM", "0300"); int hours = 0, minutes = 0; switch (hhmm.length()) { case 0: hours = 3; minutes = 0; break; case 1: hours = parseInt(hhmm); minutes = 0; break; case 2: hours = parseInt(hhmm); minutes = 0; break; case 3: hours = parseInt(hhmm.substring(0, 1)); minutes = parseInt(hhmm.substring(1)); break; case 4: hours = parseInt(hhmm.substring(0, 2)); minutes = parseInt(hhmm.substring(2)); break; default: hours = 3; minutes = 0; } if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " after_midnight_HHMM:" + hhmm); } if ((level != null) && (level.compareTo(WARNING) >= 0)) { System.out.println(PREFIX + " hours:" + hours + " minutes:" + minutes); } Calendar date = new GregorianCalendar(); date.clear(Calendar.SECOND); date.clear(Calendar.MILLISECOND); date.set(Calendar.HOUR_OF_DAY, hours); date.set(Calendar.MINUTE, minutes); long current = System.currentTimeMillis(); long dateTime = date.getTimeInMillis(); long next = current > dateTime ? dateTime + frequency : dateTime; date.setTimeInMillis(next); exec.scheduleAtFixedRate(new RestartProcess(), date.getTime(), frequency, MILLISECONDS); System.out.println(PREFIX + " exiting Restart startup"); } // This method is called when the plugin should shutdown public void stop() { exec.shutdownNow(); } // This method is called after plugin shutdown to free any resources // used by the plugin public void destroy() { startup = null; level = null; exec = null; } // Returns the names of the settings for this plugin public String[] getConfigSettings() { String[] result = getSubpropertiesThatAreLeaves(); Arrays.sort(result); return result; } // Returns the current value of the specified setting for this plugin public String getConfigValue(String propertyName) { return getProperty(propertyName, null); } // Returns the current value of the specified multichoice setting for // this plugin public String[] getConfigValues(String string) { return null; } // Returns one of the constants above that indicates what type of value // is used for a specific settings public int getConfigType(String propertyName) { if ("cmd".equals(propertyName)) { return CONFIG_TEXT; } else if ("reboot_with_clients_connected".equals(propertyName)) { return CONFIG_BOOL; } else if ("reboot_with_extenders_connected".equals(propertyName)) { return CONFIG_BOOL; } else if ("logging_level".equals(propertyName)) { return CONFIG_TEXT; } else if ("minutes_until_retry".equals(propertyName)) { return CONFIG_INTEGER; } else if ("minutes_before_recording".equals(propertyName)) { return CONFIG_INTEGER; } else if ("frequency_HH".equals(propertyName)) { return CONFIG_INTEGER; } else if ("after_midnight_HHMM".equals(propertyName)) { return CONFIG_INTEGER; } else { return CONFIG_TEXT; } } // Sets a configuration value for this plugin public void setConfigValue(String propertyName, String value) { setProperty(propertyName, value); } // Sets a configuration values for this plugin for a multiselect choice public void setConfigValues(String string, String[] strings) { } // For CONFIG_CHOICE settings; this returns the list of choices public String[] getConfigOptions(String string) { return null; } // Returns the help text for a configuration setting public String getConfigHelpText(String propertyName) { if ("cmd".equals(propertyName)) { return "Command to execute on a schedule"; } else if ("reboot_with_clients_connected".equals(propertyName)) { return "Reboot with clients connected"; } else if ("reboot_with_extenders_connected".equals(propertyName)) { return "Reboot with extenders or placeshifters connected"; } else if ("logging_level".equals(propertyName)) { return "Current logging level of this plugin"; } else if ("minutes_until_retry".equals(propertyName)) { return "Minutes to wait until retrying command"; } else if ("minutes_before_recording".equals(propertyName)) { return "Execute command at least this many minutes before a " + "recording starts"; } else if ("frequency_HH".equals(propertyName)) { return "How often to run command in hours"; } else if ("after_midnight_HHMM".equals(propertyName)) { return "Time since midnight to execute command in Hours and " + "Minutes (0-23 hours, 0-59 minutes)"; } else { return null; } } // Returns the label used to present this setting to the user public String getConfigLabel(String propertyName) { if ("cmd".equals(propertyName)) { return "Command"; } else if ("reboot_with_clients_connected".equals(propertyName)) { return "Reboot with clients"; } else if ("reboot_with_extenders_connected".equals(propertyName)) { return "Reboot with extenders"; } else if ("logging_level".equals(propertyName)) { return "Logging level"; } else if ("minutes_until_retry".equals(propertyName)) { return "Retry minutes"; } else if ("minutes_before_recording".equals(propertyName)) { return "Window minutes"; } else if ("frequency_HH".equals(propertyName)) { return "Frequency hours"; } else if ("after_midnight_HHMM".equals(propertyName)) { return "Run command when"; } else { return null; } } // Resets the configuration of this plugin public void resetConfig() { setProperty("reboot_with_clients_connected", "false"); setProperty("reboot_with_extenders_connected", "false"); setProperty("logging_level", "WARNING"); setProperty("minutes_until_retry", "15"); setProperty("minutes_before_recording", "15"); setProperty("frequency_HH", "24"); setProperty("cmd", "shutdown.exe /r"); setProperty("after_midnight_HHMM", "0300"); } private void initConfig() { String prop = getProperty("reboot_with_clients_connected", null); if (prop == null) { setProperty("reboot_with_clients_connected", "false"); } prop = getProperty("reboot_with_extenders_connected", null); if (prop == null) { setProperty("reboot_with_extenders_connected", "false"); } prop = getProperty("logging_level", null); if (prop == null) { setProperty("logging_level", "WARNING"); } prop = getProperty("minutes_until_retry", null); if (prop == null) { setProperty("minutes_until_retry", "15"); } prop = getProperty("minutes_before_recording", null); if (prop == null) { setProperty("minutes_before_recording", "15"); } prop = getProperty("frequency_HH", null); if (prop == null) { setProperty("frequency_HH", "24"); } prop = getProperty("cmd", null); if (prop == null) { setProperty("cmd", "shutdown.exe /r"); } prop = getProperty("after_midnight_HHMM", null); if (prop == null) { setProperty("after_midnight_HHMM", "0300"); } } // This is a callback method invoked from the SageTV core for any events // the listener has subscribed to. // See the sage.SageTVPluginRegistry interface definition for details // regarding subscribing and unsubscribing to events. // The eventName will be a predefined String which indicates the event type // The eventVars will be a Map of variables specific to the event // information. This Map should NOT be modified. // The keys to the eventVars Map will generally be Strings; but this // may change in the future and plugins that submit events // are not required to follow that rule. public void sageEvent(String string, Map map) { } }