* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
+ */
package org.wamblee.mythtv;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.quartz.SchedulerException;
import org.wamblee.general.BeanKernel;
/**
- *f
+ * f
*/
public class Application implements ServletContextListener {
private static final Log LOG = LogFactory.getLog(Application.class);
-
- /* (non-Javadoc)
+
+ /*
+ * (non-Javadoc)
+ *
* @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
public void contextInitialized(ServletContextEvent arg0) {
- LOG.info("initializing");
- BeanKernel.getBeanFactory();
+ LOG.info("initializing");
+ try {
+ BeanKernel.getBeanFactory().find(MonitorScheduler.class)
+ .initialize();
+ } catch (SchedulerException e) {
+ LOG.error("Error starting scheduler", e);
+ }
}
-
- /* (non-Javadoc)
+ /*
+ * (non-Javadoc)
+ *
* @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent)
*/
public void contextDestroyed(ServletContextEvent arg0) {
LOG.info("terminating");
+ try {
+ BeanKernel.getBeanFactory().find(MonitorScheduler.class)
+ .shutdown();
+ } catch (SchedulerException e) {
+ LOG.error("Error stopping scheduler", e);
+ }
}
}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.mythtv;
+
+/**
+ *
+ */
+public class Channel {
+
+ private int _id;
+
+ private String _name;
+
+ protected Channel() {
+ // Empty
+ }
+
+ /**
+ * @return the id
+ */
+ public int getId() {
+ return _id;
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "Channel(" + _id + "," + _name + ")";
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObj) {
+ if ( !(aObj instanceof Channel)) {
+ return false;
+ }
+ Channel recording = (Channel)aObj;
+ return _id == recording._id;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return _id;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.mythtv;
+
+/**
+ *
+ */
+public enum FileType {
+
+ MPG, AVI;
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.mythtv;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.wamblee.io.SimpleProcess;
+import org.wamblee.io.DirectoryMonitor.Listener;
+
+/**
+ * Link structure.
+ */
+public class LinkStructure implements Listener {
+
+ private static final Log LOG = LogFactory.getLog(LinkStructure.class);
+
+ private String _monitorDir = "/mnt/vcr";
+
+ private File _linkDir;
+
+ private RecordingDatabase _database;
+
+ private SimpleDateFormat _format;
+
+ private Map<File,Recording> _recordings;
+
+ public LinkStructure(String aMonitorDir, File aLinkDir,
+ RecordingDatabase aDatabase) {
+ _monitorDir = aMonitorDir + "/";
+ _linkDir = aLinkDir;
+ _database = aDatabase;
+ _format = new SimpleDateFormat("yyyy-MM-dd-HH:mm");
+ _recordings = new HashMap<File,Recording>();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.DirectoryMonitor.Listener#fileChanged(java.io.File)
+ */
+ public void fileChanged(File aFile) {
+ LOG.debug("file changed " + aFile);
+
+ // Re-assess file type
+ Recording recording = _recordings.get(aFile);
+ LOG.debug("Recording changed " + recording);
+ String dir = getDirectory(recording);
+ FileType type = getFileType(aFile);
+ String path = dir + "/" + getFilename(recording, type);
+
+ if (exists(dir + "/" + getFilename(recording, type))) {
+ // Nothing to do.
+ } else {
+ mkdir(dir);
+ for (FileType t : FileType.values()) {
+ rmlink(dir + "/" + getFilename(recording, t));
+ }
+ createSymLink(_monitorDir + aFile.getName(), dir + "/"
+ + getFilename(recording, type));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.DirectoryMonitor.Listener#fileCreated(java.io.File)
+ */
+ public void fileCreated(File aFile) {
+ LOG.debug("file created " + aFile);
+ Recording recording = _database.findRecording(aFile.getName());
+ _recordings.put(aFile, recording);
+ LOG.info("New recording detected " + aFile + " "
+ + recording);
+
+ recording.setFilesize(aFile.length());
+ _database.update(recording);
+ String dir = getDirectory(recording);
+ mkdir(dir);
+ createSymLink(_monitorDir + aFile.getName(), dir + "/"
+ + getFilename(recording, getFileType(aFile)));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.wamblee.io.DirectoryMonitor.Listener#fileDeleted(java.io.File)
+ */
+ public void fileDeleted(File aFile) {
+ LOG.debug("file deleted " + aFile);
+ Recording recording = _recordings.get(aFile);
+ _recordings.remove(recording);
+ LOG.info("recording deleted " + recording);
+ String dir = getDirectory(recording);
+ for (FileType t: FileType.values()) {
+ rmlink(dir + "/" + getFilename(recording, t));
+ }
+ rmdir(dir);
+ }
+
+ private String getDirectory(Recording aRecording) {
+ return aRecording.getTitle();
+ }
+
+ private FileType getFileType(File aFile) {
+ SimpleProcess process = new SimpleProcess(new File(_monitorDir), new String[] {
+ "file", aFile.getName() });
+ try {
+ process.run();
+ if (process.getStdout().contains("RIFF")) {
+ return FileType.AVI;
+ } else {
+ return FileType.MPG;
+ }
+ } catch (IOException e) {
+ LOG.error("Determining filetype for " + aFile + " failed", e);
+ return FileType.MPG;
+ }
+ }
+
+ private String getFilename(Recording aRecording, FileType aType) {
+ return _format.format(aRecording.getProgstart()) + "-"
+ + aRecording.getSubtitle() + "-"
+ + aRecording.getChannel().getName() + "."
+ + aType.toString().toLowerCase();
+ }
+
+ private boolean exists(String aPath) {
+ LOG.debug("exists " + aPath);
+ return new File(_linkDir, aPath).exists();
+ }
+
+ private void rmlink(String aPath) {
+ LOG.debug("rmlink " + aPath);
+ File link = new File(_linkDir, aPath);
+ //if ( !link.exists()) {
+ // return;
+ // }
+ if (!link.delete()) {
+ LOG.warn("Delete failed: " + aPath);
+ } else {
+ LOG.info("Removed link " + link);
+ }
+ }
+
+ private void mkdir(String aDir) {
+ LOG.debug("mkdir " + aDir);
+ File dir = new File(_linkDir, aDir);
+ if ( dir.isDirectory()) {
+ return;
+ }
+ if (!dir.mkdirs()) {
+ LOG.warn("Could not create directory path: " + aDir);
+ } else {
+ LOG.info("Created directory " + dir);
+ }
+ }
+
+ private void rmdir(String aDir) {
+ LOG.debug("rmdir " + aDir);
+ File dir = new File(_linkDir, aDir);
+ if (!dir.delete()) {
+ LOG.warn("Directory not deleted (still recordings left): " + aDir);
+ } else {
+ LOG.info("Directory " + dir + " deleted.");
+ }
+ }
+
+ private void createSymLink(String aTarget, String aSource) {
+ try {
+ SimpleProcess process = new SimpleProcess(_linkDir, new String[] {
+ "ln", "-s", aTarget, aSource });
+ process.run();
+ LOG.info("Created symlink " + aSource + " -> " + aTarget);
+ } catch (IOException e) {
+ LOG.error(
+ "Could not create symlink: " + aTarget + " <- " + aSource,
+ e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.mythtv;
+
+import java.io.File;
+import java.util.Date;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SchedulerFactory;
+import org.quartz.Trigger;
+import org.quartz.TriggerUtils;
+import org.quartz.impl.StdSchedulerFactory;
+
+/**
+ *
+ */
+public class MonitorScheduler {
+
+ private static final Log LOG = LogFactory.getLog(MonitorScheduler.class);
+ private static final String JOB_NAME = "vcrmonitor";
+ private static final String TRIGGER_NAME = "trigger";
+
+ private Scheduler _scheduler;
+ private int _intervalSeconds;
+
+ public MonitorScheduler(int aInterval) throws SchedulerException {
+ SchedulerFactory schedulerFactory = new StdSchedulerFactory();
+ _scheduler = schedulerFactory.getScheduler();
+ _intervalSeconds = aInterval;
+ }
+
+ public void initialize() throws SchedulerException {
+ LOG.info("Starting scheduler");
+ _scheduler.start();
+
+ JobDetail jobDetail = new JobDetail(JOB_NAME, null, PollDirectoryJob.class);
+ Trigger trigger = TriggerUtils.makeSecondlyTrigger(_intervalSeconds);
+ //trigger.setStartTime(TriggerUtils.getEvenHourDate(new Date()));
+ trigger.setStartTime(new Date());
+ trigger.setName(TRIGGER_NAME);
+
+ _scheduler.scheduleJob(jobDetail, trigger);
+ }
+
+ public void shutdown() throws SchedulerException {
+ LOG.info("Stopping scheduler");
+ _scheduler.shutdown();
+ }
+}
public class MythtvHibernateMappings extends HibernateMappingFiles {
public MythtvHibernateMappings() {
- // Empty
+ super(new String[] { "Channel.hbm.xml", "Recording.hbm.xml" });
}
}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.mythtv;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.StatefulJob;
+import org.wamblee.general.BeanKernel;
+import org.wamblee.io.DirectoryMonitor;
+
+/**
+ *
+ */
+public class PollDirectoryJob implements StatefulJob {
+
+ private static final Log LOG = LogFactory.getLog(PollDirectoryJob.class);
+
+ public PollDirectoryJob() {
+ // Empty
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
+ */
+ public void execute(JobExecutionContext aContext)
+ throws JobExecutionException {
+ try {
+ DirectoryMonitor monitor = BeanKernel.getBeanFactory().find(
+ DirectoryMonitor.class);
+ monitor.poll();
+ } catch (Throwable t) {
+ LOG
+ .error(
+ "something terrible happend, ignoring it and hoping for the best",
+ t);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.mythtv;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ *
+ */
+public class Recording implements Serializable {
+
+ private Channel _channel;
+ private Date _starttime;
+
+
+ private String _basename;
+
+ private Date _progstart;
+
+ private String _title;
+
+ private String _subtitle;
+
+ private long _filesize;
+
+ protected Recording() {
+ // Empty
+ }
+
+ /**
+ * @return the channel
+ */
+ public Channel getChannel() {
+ return _channel;
+ }
+
+ /**
+ * @return the basename
+ */
+ public String getBasename() {
+ return _basename;
+ }
+
+ /**
+ * @return the progstart
+ */
+ public Date getStartTime() {
+ return _starttime;
+ }
+
+ /**
+ * @return the progstart
+ */
+ public Date getProgstart() {
+ return _progstart;
+ }
+
+ /**
+ * @return the title
+ */
+ public String getTitle() {
+ return _title;
+ }
+
+ /**
+ * @return the subtitle
+ */
+ public String getSubtitle() {
+ return _subtitle;
+ }
+
+ /**
+ * @return the filesize
+ */
+ public long getFilesize() {
+ return _filesize;
+ }
+
+ public void setFilesize(long aFilesize) {
+ _filesize = aFilesize;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "Recording(" + _channel + "," + _basename + "," + _progstart + "," + _title + "," + _subtitle + ")";
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object aObj) {
+ if ( !(aObj instanceof Recording)) {
+ return false;
+ }
+ Recording recording = (Recording)aObj;
+ return _channel.equals(recording._channel) && _starttime.equals(recording._starttime);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return _channel.hashCode()*10 + ((int)_starttime.getTime() %10);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright 2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wamblee.mythtv;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+import org.hibernate.criterion.Expression;
+import org.springframework.orm.hibernate3.HibernateCallback;
+import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
+
+/**
+ *
+ */
+public class RecordingDatabase extends HibernateDaoSupport {
+
+ private static final Log LOG = LogFactory.getLog(RecordingDatabase.class);
+
+ public RecordingDatabase() {
+ // Empty
+ }
+
+ public void init() {
+ /*
+ for (Recording recording: (List<Recording>)getHibernateTemplate().loadAll(Recording.class) ) {
+ LOG.info("Found recording " + recording);
+ }
+ LOG.info("After listing recordings");
+ */
+ }
+
+ public Recording findRecording(final String aName) {
+ List<Recording> result = (List<Recording>) getHibernateTemplate().execute(new HibernateCallback() {
+ /* (non-Javadoc)
+ * @see org.springframework.orm.hibernate3.HibernateCallback#doInHibernate(org.hibernate.Session)
+ */
+ public Object doInHibernate(Session aSession) throws HibernateException, SQLException {
+ return aSession.createCriteria(Recording.class).add(Expression.eq("_basename", aName)).list();
+ }
+ });
+ if ( result.size() > 1 ) {
+ throw new RuntimeException("More than two recordings returned");
+ }
+ if ( result.size() == 0 ) {
+ return null;
+ }
+ return result.get(0);
+ }
+
+ public void update(Recording aRecording) {
+ getHibernateTemplate().update(aRecording);
+ }
+}