(no commit message)
authorerik <erik@77661180-640e-0410-b3a8-9f9b13e6d0e0>
Fri, 9 Feb 2007 23:03:12 +0000 (23:03 +0000)
committererik <erik@77661180-640e-0410-b3a8-9f9b13e6d0e0>
Fri, 9 Feb 2007 23:03:12 +0000 (23:03 +0000)
mythtv/src/main/java/org/wamblee/mythtv/Application.java
mythtv/src/main/java/org/wamblee/mythtv/Channel.java [new file with mode: 0644]
mythtv/src/main/java/org/wamblee/mythtv/FileType.java [new file with mode: 0644]
mythtv/src/main/java/org/wamblee/mythtv/LinkStructure.java [new file with mode: 0644]
mythtv/src/main/java/org/wamblee/mythtv/MonitorScheduler.java [new file with mode: 0644]
mythtv/src/main/java/org/wamblee/mythtv/MythtvHibernateMappings.java
mythtv/src/main/java/org/wamblee/mythtv/PollDirectoryJob.java [new file with mode: 0644]
mythtv/src/main/java/org/wamblee/mythtv/Recording.java [new file with mode: 0644]
mythtv/src/main/java/org/wamblee/mythtv/RecordingDatabase.java [new file with mode: 0644]

index d2135639c7df9df72152cdca0c39fb085ab26782..01d030262d5bb1791e8d2865b4ebde4c42e7b084 100644 (file)
@@ -12,7 +12,7 @@
  * 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;
 
@@ -21,27 +21,42 @@ import javax.servlet.ServletContextListener;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.quartz.SchedulerException;
 import org.wamblee.general.BeanKernel;
 
 /**
- *
+ * 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);
+        }
     }
 }
diff --git a/mythtv/src/main/java/org/wamblee/mythtv/Channel.java b/mythtv/src/main/java/org/wamblee/mythtv/Channel.java
new file mode 100644 (file)
index 0000000..104e3aa
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/mythtv/src/main/java/org/wamblee/mythtv/FileType.java b/mythtv/src/main/java/org/wamblee/mythtv/FileType.java
new file mode 100644 (file)
index 0000000..c27e290
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * 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;
+}
diff --git a/mythtv/src/main/java/org/wamblee/mythtv/LinkStructure.java b/mythtv/src/main/java/org/wamblee/mythtv/LinkStructure.java
new file mode 100644 (file)
index 0000000..868ab09
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * 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);
+        }
+    }
+
+}
diff --git a/mythtv/src/main/java/org/wamblee/mythtv/MonitorScheduler.java b/mythtv/src/main/java/org/wamblee/mythtv/MonitorScheduler.java
new file mode 100644 (file)
index 0000000..321a1c2
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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(); 
+    }
+}
index b555fb5243e2b5e5a08e1eba5fbf93b56da1b833..481afe62f7e35aefb673386525a3c145927b359b 100644 (file)
@@ -24,6 +24,6 @@ import org.wamblee.persistence.hibernate.HibernateMappingFiles;
 public class MythtvHibernateMappings extends HibernateMappingFiles {
 
     public MythtvHibernateMappings() { 
-        // Empty
+        super(new String[] { "Channel.hbm.xml", "Recording.hbm.xml" }); 
     }
 }
diff --git a/mythtv/src/main/java/org/wamblee/mythtv/PollDirectoryJob.java b/mythtv/src/main/java/org/wamblee/mythtv/PollDirectoryJob.java
new file mode 100644 (file)
index 0000000..e75ed11
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * 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);
+        }
+    }
+
+}
diff --git a/mythtv/src/main/java/org/wamblee/mythtv/Recording.java b/mythtv/src/main/java/org/wamblee/mythtv/Recording.java
new file mode 100644 (file)
index 0000000..cab43a6
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * 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); 
+    }
+
+}
diff --git a/mythtv/src/main/java/org/wamblee/mythtv/RecordingDatabase.java b/mythtv/src/main/java/org/wamblee/mythtv/RecordingDatabase.java
new file mode 100644 (file)
index 0000000..7ff5170
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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);
+    }
+}