Commits

Guido Draheim committed 24d6992 Merge

merge

  • Participants
  • Parent commits 285a106, 40b0cc9
  • Branches dvbcronrecording
  • Tags dvbcronrecording-0.4.15

Comments (0)

Files changed (19)

+## DvbCronRecording ##
+
+This TracPlugin maintains a database of recordings.
+Upon "Activate!" it creates a recording script. 
+The actual recording is performed by a tuning the
+LinuxDVB device started by a CronTab call.
+
+The system grew out of some local command-line scripts
+that would convert a list of recording times, channel
+info and program title into a matching recording script
+along with a CronTab entry. Transforming it into a
+TracPlugin allows for easier installation on a remote
+recorder box as well as sharing the recorder by 
+multiple persons with just a web access to the recorder
+box.
+
+## Download ##
+
+Get a source snapshot from one of the tags at Bitbucket:   
+<https://bitbucket.org/guidod/trac-dvbcronrecording-plugin/downloads/>
+
+Prebuilt RPMs are available from the Open Build Service:   
+<https://build.opensuse.org/package/show?project=home:guidod:tools:scm&package=trac-dvbcronrecording-plugin>
+

File src/GNUmakefile

 	test -d $(RPMROOT)/SPECS || mkdir $(RPMROOT)/SPECS
 	- test -d $(BUILDDIR)/$(PKG) && rm -rf $(BUILDDIR)/*
 	cp $(SPECNAME).spec $(RPMROOT)/SPECS/$(SPECNAME).spec
-	$(RPMBUILD) -ba $(SPECNAME).spec
+	revision=`hg summary| sed -e '/parent:/!d' -e 's/parent: *//' -e 's/:.*//'` \
+	; if [ -n "$$revision" ]; then set -x \
+	;   sed -i -e "s/revision 0/revision $$revision/"  $(RPMROOT)/SPECS/$(SPECNAME).spec \
+	; else today=`date +%Y%m%d` \
+	;   sed -i -e "s/revision 0/revision 0.$$today/"  $(RPMROOT)/SPECS/$(SPECNAME).spec \
+	; fi
+	$(RPMBUILD) -ba $(RPMROOT)/SPECS/$(SPECNAME).spec
 
 update:
-	@ version=`cat $(SPECNAME).spec | sed -e '/define _version/!d' -e 's/.*_version *//'` \
-	; package=`cat $(SPECNAME).spec | sed -e '/Name:/!d' -e 's/Name: *//'` \
-	; echo rpm -U --force $(RPMROOT)/RPMS/noarch/$$package-$$version*.noarch.rpm \
-	;      rpm -U --force $(RPMROOT)/RPMS/noarch/$$package-$$version*.noarch.rpm
+	@ spec="$(RPMROOT)/SPECS/$(SPECNAME).spec" \
+	; version=`cat "$$spec" | sed -e '/define _version/!d' -e 's/.*_version *//'` \
+	; package=`cat "$$spec" | sed -e '/Name:/!d' -e 's/Name: *//'` \
+	; rev=`cat "$$spec" | sed -e '/define revision/!d' -e 's/.*revision *//' -e 's/ .*//'` \
+	; echo rpm -U --force $(RPMROOT)/RPMS/noarch/$$package-$$version.$$rev*.noarch.rpm \
+	;      rpm -U --force $(RPMROOT)/RPMS/noarch/$$package-$$version.$$rev*.noarch.rpm
 	   
 trac restart:
 	sudo /sbin/service tracd restart

File src/dvbcronrecording/channels.de.po

 msgid "channel.conf show"
 msgstr "channel.conf Ansicht"
 
+msgid "channel.conf text"
+msgstr "channel.conf Text"
+
 msgid "action buttons"
 msgstr "Betriebsschalter"
 

File src/dvbcronrecording/channels.py

 from translate import Translate
 from tuning import tuning_adapter_list
 
+from db.session import db_cnx, commit #@UnresolvedImport
+from db.schema import RecordingChannels, RecordingChannelsConf
+import sqlalchemy as _as
+
+
 PACKAGE = 'dvbcronrecording'
 NAV = 'recordings'
 URL = 'recording'
     logg.addHandler(logging.FileHandler("/tmp/DvbCronRecordingChannelsPlugin.log"))
     logg.setLevel(logging.DEBUG)
 
-from db import schema
-def affinity(table_name, column_name):
-    global tables
-    for table in schema.tables:
-        if table.name == table_name:
-            for column in table.columns:
-                if column.name == column_name:
-                    return column.type
-    return None
-def aff(value, table_name, column_name):
-    if value is None: return None
-    typed = affinity(table_name, column_name)
-    if typed in [ "int", "INT", "integer", "INTEGER"]:
-        return intnull(value)
-    return ustr(value)
-def intnull(value, default = None):
-    if value is None: return default
-    try: return int(value)
-    except Exception: return default
-def lookup(lists, entry, default = None):
-    if entry is None: return default
-    idx = intnull(entry)
-    if idx is None or idx >= len(lists): return default
-    return lists[idx]
-def ustr(text):
-    if type(text) is unicode: return text
-    if type(text) is str: return unicode(text)
-    return unicode(str(text))
-
 class MakefileEntry: pass
 class FileEntry: pass
 
         req.perm.assert_permission(CHANNELS_VIEW)
         translate = Translate("channels", req.locale)
 
-        # getting cursor
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
         page = req.args['page']
         if not page: page = "list"
         
         # ------------------------------------------------
         message = ""
         
+        if page in [ "text" ]:
+            return self.show_text(req, page)
+        args = req.args
         if page in [ None, '', 'list' ]:
             pass
         if page in [ 'newentry', 'append' ]:
-            message = self._channel_append(cursor, req)
+            message = self._channel_append(req)
+            args = {}
         if page in [ 'update' ]:
-            message = self._channel_update(cursor, req)
+            message = self._channel_update(req)
+            args = {}
         if page in [ 'delete' ]:
-            message = self._channel_delete(cursor, req)
+            message = self._channel_delete(req)
+            args = {}
         
         # database commit and return page content
-        db.commit()
+        commit()
 
-        add_stylesheet(req, 'common/css/wiki.css')
-        add_stylesheet(req, PACKAGE+'/css/recordingchannels.css')
-        add_script(req, 'common/js/trac.js')
-        add_script(req, 'common/js/wikitoolbar.js')
-        
+       
         if not message: message = translate("please do not use umlauts")
         message += translate("?problem")
-        newchannel_list = self._newchannel_list(cursor, req)
-        channel_list = self._channel_list(cursor, req)
+        newchannel_list = self._newchannel_list(req)
+        channel_list = self._channel_list(req.args)
 
         # passing variables to template
         data = {}
         data['message'] = message
         data['title'] = translate('Channel List')
-        data['adapterlist'] = self._adapter_list(cursor, req)
+        data['adapterlist'] = self.adapter_list(req)
+        data["adapter"] = req.args.get("adapter", "0")
         data['new_datalist'] = newchannel_list
         data["_pagenum"] = req.args.get("_pagenum", "0")
         data["_pagesize"] = req.args.get("_pagesize", "10")
         data['appends'] = req.perm.has_permission(CHANNELS_VIEW);
         data['author'] = req.authname or ""
         data['_'] = translate
+        add_stylesheet(req, 'common/css/wiki.css')
+        add_stylesheet(req, PACKAGE+'/css/recordingchannels.css')
+        add_script(req, 'common/js/trac.js')
+        add_script(req, 'common/js/wikitoolbar.js')
+        return ('channels_list.html', data, None)
 
-        return ('channels_list.html', data, None)
+    def show_text(self, req, page):
+        # passing variables to template
+        translate = Translate("channels", req.locale)
+        adapter = req.args["adapter"]
+        conf = self.channelsconf_for_adapter(adapter)
+        data = {}
+        data['message'] = "" 
+        data['title'] = translate('channels.conf text') + adapter
+        data['adapterlist'] = self.adapter_list(req)
+        data["adapter"] = adapter
+        data['text'] = "\n".join(conf)
+        data["rows"] = len(conf)
+        data['_'] = translate
+        add_stylesheet(req, 'common/css/wiki.css')
+        add_stylesheet(req, PACKAGE+'/css/recordingchannels.css')
+        add_script(req, 'common/js/trac.js')
+        add_script(req, 'common/js/wikitoolbar.js')
+        return ('channels_text.html', data, None)
 
     # ====================================================================
     
     """
       Returns list of the adapter settings
     """
-    def _adapter_list(self, cursor, req, defaults = [ 0 ]):
-        return tuning_adapter_list(cursor, defaults)
+    def adapter_list(self, req, defaults = [ 0 ]):
+        return tuning_adapter_list(self.env, defaults)
     
 
-    def _newchannel_list(self, cursor, req):
+    def _newchannel_list(self, req):
         translate = Translate("channels", req.locale)
         _ = ['channelname', 'adapter', 'title']
         defs = {}
     """
       Returns list of the channels
     """
-    def _channel_list(self, cursor, req):
-        return channel_list(cursor) # no req.args here
+    def _channel_list(self, args = {}):
+        q = db_cnx(self.env).query(RecordingChannels)
+        q = q.order_by("channelname", "title", "adapter")
+        cols = ['id', 'channelname', 'adapter', 'title']
+        for col in cols:
+            if args and col in args:
+                q = q.filter_by(**{col : args[col]})
+        return q.all()
 
     """
       Appends a new entry into channel list
     """
-    def _channel_append(self, cursor, req):
-        cols = ['channelname', 'adapter', 'title']
-        vals = [] # { "author" : req.authname or 'anonymous' }
-        for col in cols:
-            if req.args.has_key(col):
-                vals += [ aff(req.args[col], "recording_channels", col) ]
-            else:
-                vals += [ None ]
-        sql = "INSERT INTO recording_channels (%s)" % ",".join(cols)
-        sql += " VALUES (%s)" % (",".join(["%s"] * len(cols)))
-        cursor.execute(sql, vals)
+    def _channel_append(self, req):
+        item = RecordingChannels()
+        item.channelname = req.args.get("channelname")
+        item.adapter = req.args.get("adapter")
+        item.title = req.args.get("title")
+        session = db_cnx(self.env)
+        session.add(item)
+        session.flush()
         return "OK"
 
     """
       Updates an entry in the channel list
     """
-    def _channel_update(self, cursor, req):
-        keys = ['id']
-        cols = ['channelname', 'adapter', 'title']
-        vals = [] # { "author" : req.authname or 'anonymous' }
-        for col in cols:
-            if req.args.has_key(col):
-                vals += [ aff(req.args[col], "recording_channels", col) ]
-            else:
-                vals += [ None ] 
-        where = []
-        for col in keys:
-            if req.args.has_key(col):
-                where += [ aff(req.args[col], "recording_channels", col) ]
-            else:
-                where += [ None ]
-        sqlX = ""
-        try:
-            sql = "UPDATE recording_channels " 
-            sql += " SET "+(",".join([" %s = %%s" % col for col in cols]))
-            sql += " WHERE " + (" AND ".join([" %s = %%s" % col for col in keys ]))
-            sqlX = sql + " | " + str( vals + where )
-            cursor.execute(sql, vals + where)
-        except Exception, e:
-            return "ERROR: %s\n%s" % (str(e), sqlX)
+    def _channel_update(self, req):
+        session = db_cnx(self.env)
+        item =  session.query(RecordingChannels).get(req.args.get("id"))
+        if not item:
+            return "ERROR: channel item not found for id=%s" % req.args.get("id")
+        item.channelname = req.args.get("channelname")
+        item.adapter = req.args.get("adapter")
+        item.title = req.args.get("title")
+        session.flush()
         return "OK"
 
     """
       Deletes entry from recordings
     """
-    def _channel_delete(self, cursor, req):
-        keys = ['id']
-        key_vals = []
-        for col in keys:
-            if req.args.has_key(col):
-                key_vals += [ aff(req.args[col], "recording_channels", col) ]
-            else:
-                key_vals += [ None ]
-        key_sql = " WHERE " + (" AND ".join([" %s = %%s" % col for col in keys ]))
-        cursor.execute("DELETE FROM recording_channels "+key_sql, key_vals)
+    def _channel_delete(self, req):
+        session = db_cnx(self.env)
+        q = session.query(RecordingChannels)
+        q = q.filter_by(id = req.args.get("id")).delete()
 
-"""
-  Returns list of the channels
-"""
-def channel_list(cursor, args = {}):
-    view = [ "channelname", "adapter" ]
-    cols = ['id', 'channelname', 'adapter', 'title']
-    order = ["channelname", "title", "adapter"]
-    sql = "SELECT %s FROM recording_channels" % (",".join(cols))
-    order_sql = " ORDER BY %s" % (",".join(order))
-    view_vals = []
-    view_cols = []
-    for col in view:
-        if args.has_key(col):
-            view_vals += [ aff(args[col], "recording_channels", col) ]
-            view_cols += [ col ]
-    if view_cols:
-        view_sql = " WHERE " + (" AND ".join([" %s = %%s" % col for col in view_cols ]))
-        cursor.execute(sql + view_sql + order_sql, view_vals)
-    else:
-        cursor.execute(sql + order_sql)
-    entries = []
-    for entry in cursor:
-        entry = dict(zip(cols, entry))
-        entries.append(entry)
-    return entries
+    def channelname_list(self, req, defaults = None):
+        """ Returns list of the adapter settings """
+        session = db_cnx(self.env)
+        if defaults is None:
+            translate = Translate("channels", req.locale)
+            _defaults = translate.get("-default-channels", "3sat")
+            defaults = [ item.strip() for item in _defaults.split(",") ]
+        cols = ['channelname']
+        sql = "SELECT DISTINCT %s FROM recording_channels" % (",".join(cols))
+        sql += " ORDER BY "+(",".join(cols))
+        result = session.execute(sql)
+        entries = []
+        for entry in result:
+            entries.append(entry[0])
+        for channelname in defaults:
+            if channelname not in entries:
+                entries.append(channelname)
+        return entries
+    def channelname_to_channelconflist(self, req, channelname):
+        """ input is a channelname and the result is an object list with
+            attributes adapter + channel title. A tuning operation
+            will usually specify the channel.conf for the adapter
+            along with the zap entry name to be used from that file."""
+        logg.debug("resolving %s" % channelname)
+        q = db_cnx(self.env).query(RecordingChannels)
+        q = q.order_by("channelname", "title", "adapter")
+        q = q.filter_by( channelname = channelname)
+        for entry in q.all():
+            logg.debug("   found %s" % entry.title)
+            logg.debug("      at %s\n" % entry.adapter)
+            item = RecordingChannelsItem()
+            item.adapter = entry.adapter
+            item.title = entry.title
+            item.channel = entry.title  
+            yield item
+    def channelname_list_for_adapter(self, req, adapter = 0):
+        """ Returns list of the adapter settings """
+        logg.debug("channels.conf for adapter %s" % adapter)
+        q = db_cnx(self.env).query(RecordingChannels.channelname)
+        q = q.filter_by( adapter = adapter)
+        entries = []
+        for entry in q.all():
+            entries.append(entry.channelname)
+        return sorted(entries)
+    def channelsconflist_for_adapter(self, adapter):
+        logg.debug("channels.conf for adapter %s" % adapter)
+        session = db_cnx(self.env)
+        q = session.query(RecordingChannels)
+        q = q.filter_by( adapter = adapter)
+        channels = {}
+        for entry in q.all():
+            channels[entry.title] = entry.channelname
+        q = session.query(RecordingChannelsConf)
+        q = q.filter_by( adapter = adapter )
+        for entry in q.all():
+            if entry.title in channels:
+                item = RecordingChannelsConfItem()
+                item.title = channels[entry.title]
+                item.frequency = entry.frequency
+                item.polarity = entry.polarity
+                item.source = entry.source
+                item.symbolrate = entry.symbolrate
+                item.vpid = entry.vpid
+                item.apid = entry.apid
+                item.tpid = entry.tpid
+                yield item
+    def channelsconf_for_adapter(self, adapter):
+        return list(self._channelsconf_for_adapter(adapter))
+    def _channelsconf_for_adapter(self, adapter):
+        for item in self.channelsconflist_for_adapter(adapter):
+            yield ":".join([str(item.title),
+                             str(item.frequency),
+                             str(item.polarity),
+                             str(item.source),
+                             str(item.symbolrate),
+                             str(item.vpid),
+                             str(item.apid),
+                             str(item.tpid),                             
+                             ])
 
-
-"""
-  Returns list of the adapter settings
-"""
-def channelname_list(cursor, req, defaults = None):
-    if defaults is None:
-        translate = Translate("channels", req.locale)
-        _defaults = translate.get("-default-channels", "3sat")
-        defaults = [ item.strip() for item in _defaults.split(",") ]
-    cols = ['channelname']
-    sql = "SELECT DISTINCT %s FROM recording_channels" % (",".join(cols))
-    sql += " ORDER BY "+(",".join(cols))
-    cursor.execute(sql)
-    entries = []
-    for entry in cursor:
-        entries.append(entry[0])
-    for channelname in defaults:
-        if channelname not in entries:
-            entries.append(channelname)
-    return entries
-    
-class channelconfitem: pass
-def channelname_to_channelconflist(cursor, req, channelname):
-    """ input is a channelname and the result is an object list with
-        attributes adapter + channel title. A tuning operation
-        will usually specify the channel.conf for the adapter
-        along with the zap entry name to be used from that file."""
-    logg.debug("resolving %s" % channelname)
-    for entry in channel_list(cursor, { "channelname" : channelname }):
-        logg.debug("   found %s" % entry["title"])
-        logg.debug("      at %s\n" % entry["adapter"])
-        item = channelconfitem()
-        item.adapter = entry["adapter"]
-        item.title = entry["title"]
-        item.channel = entry["title"]  
-        yield item
-
+class RecordingChannelsItem: pass
+class RecordingChannelsConfItem: pass

File src/dvbcronrecording/channelsconf.py

 from translate import Translate
 from tuning import tuning_adapter_list
 
+from db.session import db_cnx, commit #@UnresolvedImport
+from db.schema import RecordingChannelsConf
+import sqlalchemy as _as
+
+
 PACKAGE = u'dvbcronrecording'
 NAV = u'recordings'
 URL = u'recording'
 
 DEBUG = False
 
-from db import schema
-def affinity(table_name, column_name):
-    global tables
-    for table in schema.tables:
-        if table.name == table_name:
-            for column in table.columns:
-                if column.name == column_name:
-                    return column.type
-    return None
-def aff(value, table_name, column_name):
-    if value is None: return None
-    typed = affinity(table_name, column_name)
-    if typed in [ "int", "INT", "integer", "INTEGER"]:
-        return intnull(value)
-    return ustr(value)
-def intnull(value, default = None):
-    if value is None: return default
-    try: return int(value)
-    except Exception: return default
-def lookup(lists, entry, default = None):
-    if entry is None: return default
-    idx = intnull(entry)
-    if idx is None or idx >= len(lists): return default
-    return lists[idx]
 def ustr(text):
     if type(text) is unicode: return text
     if type(text) is str: return unicode(text)
         translate = Translate("channelsconf", req.locale)
 
         # getting cursor
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
         page = req.args['page']
         if not page: page = "show"
         if page in [ "save" ]:
             text = req.args["text"]
-            result = self.savetext(cursor, req, text)
-            db.commit()
+            result = self.savetext(req, text)
+            commit()
             data = {}
             data['title'] = translate("Saved")
             data['messages'] = ['(%s)' % text, result]
-            data['adapterlist'] = self._adapter_list(cursor, req)
-            data['text'] = self.loadtext(cursor, req)
+            data['adapterlist'] = self._adapter_list(req)
+            data['text'] = self.loadtext(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordingchannelsconf.css')
             add_script(req, 'common/js/trac.js')
             data = {}
             data['title'] = translate("Edit")
             data['messages'] = [ translate('use the traditional dvbscan/zap syntax') ]
-            data['adapterlist'] = self._adapter_list(cursor, req)
-            data['text'] = self.loadtext(cursor, req)
+            data['adapterlist'] = self._adapter_list(req)
+            data['text'] = self.loadtext(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordingchannelsconf.css')
             add_script(req, 'common/js/trac.js')
             return ('channelsconf_editor.html', data, None)
         if page in [ "take", "takeover" ]:
-            channels = self._channelsconf_selectby(cursor, req, ['adapter', 'title'])
+            channels = self._channelsconf_selectby(req, ['adapter', 'title'])
             data = {}
             data['title'] = translate("Takeover")
             data['messages'] = [ "" ]
-            data['adapterlist'] = self._adapter_list(cursor, req)
+            data['adapterlist'] = self._adapter_list(req)
             data["_pagenum"] = req.args.get("_pagenum", "0")
             data["_pagesize"] = req.args.get("_pagesize", "10")
             data['datalist'] = Paginator(channels, int(data["_pagenum"]), int(data["_pagesize"]))
         message = ""
         
         if page in [ 'newentry', 'append' ]:
-            message = self._channelsconf_append(cursor, req)
+            message = self._channelsconf_append(req)
         if page in [ 'update' ]:
-            message = self._channelsconf_update(cursor, req)
+            message = self._channelsconf_update(req)
         if page in [ 'delete' ]:
-            message = self._channelsconf_delete(cursor, req)
+            message = self._channelsconf_delete(req)
         
         # database commit and return page content
-        db.commit()
+        commit()
 
         add_stylesheet(req, 'common/css/wiki.css')
         add_stylesheet(req, PACKAGE+'/css/recordingchannelsconf.css')
         message += "[%s]" % channeltype
         message += "{%s}" % page
         
-        newchannels_list = self._newchannels_list(cursor, req)
-        channels_list = self._channelsconf_list(cursor, req, channeltype)
+        newchannels_list = self._newchannels_list(req)
+        channels_list = self._channelsconf_list(req, channeltype)
         channeltypelist = ["all channel types",
                            "data channels", 
                            "radio channels", 
         data['message'] = message
         data['title'] = translate('Channel List')
         data['polaritylist'] = polaritylist
-        data['adapterlist'] = self._adapter_list(cursor, req)
+        data['adapterlist'] = self._adapter_list(req)
         data["channeltype"] = channeltype
         data["channeltypelist"] = channeltypelist
         data['new_datalist'] = newchannels_list
     """
       Returns list of the adapter settings
     """
-    def _adapter_list(self, cursor, req, defaults = [ 0 ]):
-        return tuning_adapter_list(cursor, defaults)
+    def _adapter_list(self, req, defaults = [ 0 ]):
+        return tuning_adapter_list(self.env, defaults)
     
     """
       Returns list of the channels.conf
     """
-    def _channelsconf_selectby(self, cursor, req, selectby = ['adapter', 'title']):
-        view = selectby
-        cols = ['adapter', 'title','frequency','polarity','source','symbolrate','vpid', 'apid', 'tpid']
-        sql = "SELECT %s FROM recording_channelsconf" % (",".join(cols))
-        order_sql = " ORDER BY title, adapter, source LIMIT 11"
-        view_vals = []
-        view_cols = []
-        for col in view:
-            if req.args.has_key(col):
-                view_vals += [ aff(req.args[col], "recording_channelsconf", col) ]
-                view_cols += [ col ]
-        if view_cols:
-            view_sql = " WHERE " + (" AND ".join([" %s = %%s" % col for col in view_cols ]))
-            logg.info("sql = %s", sql + view_sql)
-            cursor.execute(sql + view_sql + order_sql, view_vals)
-        else:
-            logg.info("sql = %s;", sql + order_sql)
-            cursor.execute(sql)
-        entries = []
-        for entry in cursor:
-            entry = dict(zip(cols, entry))
-            entries.append(entry)
-        return entries
+    def _channelsconf_selectby(self, req, selectby = ['adapter', 'title']):
+        session = db_cnx(self.env)
+        q = session.query(RecordingChannelsConf)
+        for col in selectby:
+            if col in req.args:
+                q = q.filter_by(**{col : req.args.get(col)})
+        q = q.order_by("title", "adapter", "source")
+        return q.all()
     
-    def _newchannels_list(self, cursor, req):
+    def _newchannels_list(self, req):
         _ = ['adapter', 'title','frequency','polarity','source','symbolrate','vpid', 'apid', 'tpid']
         translate = Translate("channelsconf", req.locale)
         defs = {}
     """
       Returns list of the channels.conf
     """
-    def _channelsconf_list(self, cursor, req, channeltype = None):
+    def _channelsconf_list(self, req, channeltype = None):
+        session = db_cnx(self.env)
+        q = session.query(RecordingChannelsConf)
+        q = q.order_by("title", "adapter", "source")
         view = ['adapter']
-        cols = ['adapter', 'title','frequency','polarity','source','symbolrate','vpid', 'apid', 'tpid']
-        sql = "SELECT %s FROM recording_channelsconf" % (",".join(cols))
-        order_sql = " ORDER BY title, adapter, source"
-        view_vals = []
-        view_cols = []
-        type_cols = []
         for col in view:
             if req.args.has_key(col):
-                view_vals += [ aff(req.args[col], "recording_channelsconf", col) ]
-                view_cols += [ col ]
+                q = q.filter_by(**{ col : req.args.get(col)})
         if channeltype in [ "audio", "video", "radio channels", "video channels" ]:
-            type_cols += ["apid > 0"]
+            q = q.filter("apid > 0")
         elif channeltype in [ "data", "data channels"]:
-            type_cols += ["apid = 0"]
+            q = q.filter("apid = 0")
         if channeltype in [ "video", "video channels" ]:
-            type_cols += ["vpid > 0"]
+            q = q.filter("vpid > 0")
         elif channeltype in [ "audio", "radio channels"]:
-            type_cols += ["vpid = 0"]
-        if view_cols:
-            view_sql = " WHERE " + (" AND ".join([" %s = %%s" % col for col in view_cols ]))
-            if type_cols:
-                view_sql += " AND " + " AND ".join(type_cols)
-            logg.info("sql = %s", sql + view_sql)
-            cursor.execute(sql + view_sql + order_sql, view_vals)
-        else:
-            view_sql = ""
-            if type_cols:
-                view_sql += " WHERE " + " AND ".join(type_cols)
-            logg.info("sql = %s;", sql + order_sql)
-            cursor.execute(sql + view_sql + order_sql)
-        entries = []
-        for entry in cursor:
-            entry = dict(zip(cols, entry))
-            entries.append(entry)
-        return entries
+            q = q.filter("vpid = 0")
+        return q.all()
 
     """
       Appends a new entry into channels.conf list
     """
-    def _channelsconf_append(self, cursor, req):
-        cols = ['adapter', 'title','frequency','polarity','source','symbolrate','vpid', 'apid', 'tpid']
-        vals = [] # { "author" : req.authname or 'anonymous' }
-        for col in cols:
-            if req.args.has_key(col):
-                vals += [ aff(req.args[col], "recording_channel", col) ]
-            else:
-                vals += [ None ]
-        sql = "INSERT INTO recording_channelsconf (%s)" % ",".join(cols)
-        sql += " VALUES (%s)" % (",".join(["%s"] * len(cols)))
-        cursor.execute(sql, vals)
+    def _channelsconf_append(self, req):
+        item = RecordingChannelsConf()
+        item.frequency = req.args("frequency")
+        item.polarity = req.args("polarity")
+        item.source = req.args("source")
+        item.symbolrate = req.args("symbolrate")
+        item.vpid = req.args("vpid")
+        item.apid = req.args("apid")
+        item.tpid = req.args("tpid")
+        item.title = req.args.get("newtitle")
+        session = db_cnx(self.env)
+        session.add(item)
+        session.flush() 
         return "OK"
 
     """
       Updates an entry in the channels.conf list
     """
-    def _channelsconf_update(self, cursor, req):
+    def _channelsconf_update(self, req):
         keys = ['adapter', 'title']
-        cols = ['frequency','polarity','source','symbolrate','vpid', 'apid', 'tpid']
-        rename = { "newtitle" : "title" }
-        vals = [] # { "author" : req.authname or 'anonymous' }
-        for col in cols:
-            if req.args.has_key(col):
-                vals += [ aff(req.args[col], "recording_channelsconf", col) ]
-            else:
-                vals += [ None ] 
-        where = []
+        q = db_cnx(self.env).query(RecordingChannelsConf)
         for col in keys:
             if req.args.has_key(col):
-                where += [ aff(req.args[col], "recording_channelsconf", col) ]
-            else:
-                where += [ None ]
-        for col in rename:
-            if req.args.has_key(col):
-                col2 = rename[col]
-                vals += [ aff(req.args[col2], "recording_channelsconf", col) ]
-                cols += col2
-        sqlX = ""
+                q = q.filter_by(**{ col : req.args.get(col) })
         try:
-            sql = "UPDATE recording_channelsconf " 
-            sql += " SET "+(",".join([" %s = %%s" % col for col in cols]))
-            sql += " WHERE " + (" AND ".join([" %s = %%s" % col for col in keys ]))
-            sqlX = sql + " | " + str( vals + where )
-            cursor.execute(sql, vals + where)
+            for item in q.all():
+                item.frequency = req.args("frequency")
+                item.polarity = req.args("polarity")
+                item.source = req.args("source")
+                item.symbolrate = req.args("symbolrate")
+                item.vpid = req.args("vpid")
+                item.apid = req.args("apid")
+                item.tpid = req.args("tpid")
+                if req.args.get("newtitle"):
+                    item.title = req.args.get("newtitle") 
         except Exception, e:
-            return "ERROR: %s\n%s" % (str(e), sqlX)
+            return "ERROR: %s\n%s" % (str(e), q)
         return "OK"
 
     """
       Deletes entry from recordings
     """
-    def _channelsconf_delete(self, cursor, req):
+    def _channelsconf_delete(self, req):
+        session = db_cnx(self.env)
         keys = ['adapter', 'title']
-        key_vals = []
+        q = session.query(RecordingChannelsConf)
         for col in keys:
             if req.args.has_key(col):
-                key_vals += [ aff(req.args[col], "recording_channelsconf", col) ]
-            else:
-                key_vals += [ None ]
-        key_sql = " WHERE " + (" AND ".join([" %s = %%s" % col for col in keys ]))
-        cursor.execute("DELETE FROM recording_channelsconf "+key_sql, key_vals)
+                q = q.filter_by(**{ col : req.args.get(col) })
+        q.delete()
 
 
     # ---------------------------------------------------------------
-    def savetext(self, cursor, req, text):
+    def savetext(self, req, text):
         if not req.args.has_key("adapter"):
             return "no adapter given in update"
+        session = db_cnx(self.env)
         adapter = req.args["adapter"]
         sql = "DELETE FROM recording_channelsconf WHERE adapter = %s"
-        cursor.execute(sql, adapter)
+        session.execute(sql, adapter)
         msg = []
         for line in text.split("\n"):
-            msg += [ self.savetextline(cursor, req, unicode(line)) ]
+            msg += [ self.savetextline(req, unicode(line)) ]
         return "(%s lines = %s)" % (len(msg), str(msg))
-    def savetextline(self, cursor, req, line):
+    def savetextline(self, req, line):
         cols = ["title", "frequency", "polarity", "source", "symbolrate", "vpid","apid","tpid"]
         # VIVA PLUS:12551:v:0:22000:171:172:12120
         m = re.match(r"([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+)", line)
                     val = vals[n]
                     if val: val = val.strip()
                     req.args[ cols[n] ] = val
-            return self._channelsconf_append(cursor, req)
+            return self._channelsconf_append(req)
         else:
             return "NO."
-    def loadtext(self, cursor, req):
-        return "\n".join(list(self.loadtextlines(cursor, req)))
-    def loadtextlines(self, cursor, req):
+    def loadtext(self, req):
+        return "\n".join(list(self.loadtextlines(req)))
+    def loadtextlines(self, req):
         cols = ["title", "frequency", "polarity", "source", "symbolrate", "vpid","apid","tpid"]
-        entries = self._channelsconf_list(cursor, req)
+        entries = self._channelsconf_list(req)
         for entry in entries:
             vals = []
             for col in cols:
-                if col in entry:
-                    vals += [ ustr(entry[col]) ]
-                else:
-                    vals += ""
+                vals += [ ustr(getattr(entry, col))]
             yield (":".join(vals))

File src/dvbcronrecording/computer.py

         past_days = self.get_past_days_this_month()
         logg.info("allowed_months %s" % (str(allowed_months)))    
         for entry in entries:
-            if entry["status"] == "no": 
-                logg.debug("status disabled for %s", entry["title"]) 
+            if entry.status == "no": 
+                logg.debug("status disabled for %s", entry.title) 
                 continue
             item = RecorderItem()
-            item.channelname = entry["channelname"]
-            item.title = entry["title"]
+            item.channelname = entry.channelname
+            item.title = entry.title
             if not item.title:
                 logg.debug("item had not title") 
                 continue            
             if not item.channelname: 
                 logg.debug("no channel name for '%s", item.title) 
                 continue            
-            m = re.match("(\d+)[.](\d+)[.]", entry["onlydate"])
+            m = re.match("(\d+)[.](\d+)[.]", entry.onlydate)
             if m:
-                logg.debug("onlydate %s for %s", entry["onlydate"], item.title)
+                logg.debug("onlydate %s for %s", entry.onlydate, item.title)
                 try:
                     onlyday = intnull(m.group(1))
                     onlymonth = intnull(m.group(2))
                     item.months = [ onlymonth ]
                     logg.info("onlyday %s onlymonths %s for %s", item.onlyday, item.months, item.title)
                 except Exception:
-                    logg.debug("conversion error %s '%s'", entry["onlydate"], item.title)
-                item.weekday = intnull(entry["weekday"])
+                    logg.debug("conversion error %s '%s'", entry.onlydate, item.title)
+                item.weekday = intnull(entry.weekday)
             else: 
-                item.weekday = intnull(entry["weekday"])
+                item.weekday = intnull(entry.weekday)
                 if item.weekday is None:
                     logg.debug("item.weekday is None '%s'", item.title) 
                     continue
                 if item.weekday >= 7: item.weekday = item.weekday % 7
-            item.newtimeMMM = self.zero(self.minutes(entry["newtime"]))
-            item.endtimeMMM = self.zero(self.minutes(entry["endtime"]))
-            item.extratimeM = self.zero(self.minutes(entry["extratime"]))
-            try: item.rank = float(int(entry["priority"]))
+            item.newtimeMMM = self.zero(self.minutes(entry.newtime))
+            item.endtimeMMM = self.zero(self.minutes(entry.endtime))
+            item.extratimeM = self.zero(self.minutes(entry.extratime))
+            try: item.rank = float(int(entry.priority))
             except: pass
             if item.channelname in rankup_channels:
                 item.rank += rankup_plus
-            logg.debug("originally %s-%s '%s'" % (entry["newtime"], entry["endtime"], item.title))
+            logg.debug("originally %s-%s '%s'" % (entry.newtime, entry.endtime, item.title))
             yield item
     def recordinglist_to_recordergroups(self, entries, ahead = months_ahead):
         recorderitems = list(self.recordinglist_to_recorderitems(entries, ahead))

File src/dvbcronrecording/core.py

 import os.path
 import time
 import locale
-from urllib import quote_plus, unquote_plus
 
 from trac.core import Component, implements
 # from trac.wiki import wiki_to_html, wiki_to_oneliner
 from trac.config import Option, ListOption, PathOption, BoolOption
 
 from translate import Translate #@UnresolvedImport
-from channels import channelname_list, channelname_to_channelconflist
 from computer import TracSimpleRecorderComputer, MMMtoHHMM
 try:
-    from tuning import DvbCronRecordingTuningPlugin
+    from channels import DvbCronRecordingChannelsPlugin
 except:
-    DvbCronRecordingTuningPlugin = None
+    DvbCronRecordingChannelsPlugin = None
 
 from quoting import quote4, unquote4
 #### sudo mkdir /srv/dvbcronrecording
 RECORDING_CRON = "recording.cron"
 RECORDING_UPDATE_CRON = "recording-update.cron"
 RECORDING_UPDATE_CHANGE_CRON = "recording-update-change.cron"
+RECORDING_CHANNELSCONF = "channels%(adapter)s.conf"
 
 CHANNELSCONF = "~/.szap/{adapter}/channels.conf"
 
+from db.session import db_cnx, commit #@UnresolvedImport
+from db.schema import RecordingList
+
 def _(text): return text
 
-from db import schema
-def affinity(table_name, column_name):
-    global tables
-    for table in schema.tables:
-        if table.name == table_name:
-            for column in table.columns:
-                if column.name == column_name:
-                    return column.type
-    return None
-def aff(value, table_name, column_name):
-    if value is None: return None
-    typed = affinity(table_name, column_name)
-    if typed in [ "int", "INT", "integer", "INTEGER"]:
-        return intnull(value)
-    return ustr(value)
 def intnull(value, default = None):
     if value is None: return default
     try: return int(value)
     if type(text) is unicode: return text
     if type(text) is str: return unicode(text)
     return unicode(str(text))
-def quote_plus_str(text):
-    if type(text) is unicode: return quote_plus(text.encode("utf-8"))
-    if type(text) is str: return quote_plus(text)
-    return quote_plus(str(text))
-def unquote_plus_ustr(text):
-    return unicode(unquote_plus(text), "utf-8")    
 
 class MakefileEntry: pass
 
         req.perm.assert_permission(LIST_VIEW)
         translate = Translate("core", req.locale, req.languages )
 
-        # getting cursor
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
         page = req.args['page']
         if not page: page = "list"
         
         if page in [ 'makefile' ]:
             data = {}
             data['title'] = translate("Makefile Overview")
-            data['entries'] = self._makefile_entries(cursor, req)
+            data['entries'] = self._makefile_entries(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
             add_script(req, 'common/js/trac.js')
             data = {}
             data['title'] = translate("Cronfile Overview")
             data['messages'] = [ self.crontabreloadline() ]
-            data['cronlines'] = self._crontablines(cursor, req)
+            data['cronlines'] = self._crontablines(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
             add_script(req, 'common/js/trac.js')
             data['title'] = translate("Recording Plan")
             data['messages'] = [ ]
             data['weekdaynames'] = translate("**weekdaynames")
-            data['recordings'] = self._recording_plan(cursor, req)
+            data['recordings'] = self._recording_plan(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
             add_script(req, 'common/js/trac.js')
         if page in [ 'activate' ]:
             data = {}
             data['title'] = translate("Cron Activate")
-            makefile_entries = self._makefile_entries(cursor, req)
+            makefile_entries = self._makefile_entries(req)
             makefile_list = [ entry.rule for entry in makefile_entries  ]
-            makefile_entries = self._makefile_entries(cursor, req) # FIXME
+            makefile_entries = self._makefile_entries(req) # FIXME
             crontab_lines = self._to_crontablines(makefile_entries)
             crontab_reload = self.crontabreloadline()
             crontab_change = self.crontabchangeline()
             crontab_list = list(crontab_lines)
+            written_messages = []
+            for adapter in self.adapter_list(req):
+                filename = self.cron_channelsconf_filename(adapter)
+                conf = self.channelsconf_for_adapter(adapter)
+                if conf:
+                    written = self.writefile(req, filename, conf)
+                    written_messages += [ written ]
             a = self.writefile(req, self.cronfilename(), list(crontab_list))
             b = self.writefile(req, self.makefilename(), list(makefile_list))
             c = self.writefile(req, self.cron_update_filename(), [ crontab_reload ])
             e = translate("cron.will.be.updated.every")+(" %s" % self.cronminutes_string())
             if self.crondirectly:
                 e = self.writefile(req, self.cron_target_filename(), list(crontab_list))
-            data['messages'] = [ a, b, c, d, e ]
+            data['messages'] = written_messages + [ a, b, c, d, e ]
             data['cronlines'] = crontab_list # + [ " ============= "] + makefile_list
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
             return ('recordingcronfile.html', data, None)
         if page in [ "save" ]:
             text = req.args["text"]
-            result = self.savetext(cursor, req, text)
-            db.commit()
+            result = self.savetext(req, text)
+            commit()
             data = {}
             data['title'] = translate("Save")
             data['messages'] = ['(%s)' % text, result]
-            data['text'] = self.loadtext(cursor, req)
+            data['text'] = self.loadtext(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
             add_script(req, 'common/js/trac.js')
             data = {}
             data['title'] = translate("Edit")
             data['messages'] = ['use the traditional syntax']
-            data['text'] = self.loadtext(cursor, req)
+            data['text'] = self.loadtext(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
             add_script(req, 'common/js/trac.js')
             pass
         if page in [ 'newentry' ]:
             req.perm.assert_permission(LIST_APPEND)
-            message = self._recordings_append(cursor, req)
+            message = self._recordings_append(req)
         if page in [ 'update' ]:
             req.perm.assert_permission(LIST_APPEND)
-            message = self._recordings_update(cursor, req)
+            message = self._recordings_update(req)
         if page in [ 'delete' ]:
             req.perm.assert_permission(LIST_DELETE)
-            message = self._recordings_delete(cursor, req)
+            message = self._recordings_delete(req)
         
         # database commit and return page content
-        db.commit()
+        commit()
 
         add_stylesheet(req, 'common/css/wiki.css')
         add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
         data = {}
         data['message'] = message
         data['title'] = translate('Recordings List')
-        data['recordings'] = self._recordings_list(cursor, req)
+        data['recordings'] = self._recordings_list(req)
         data['appends'] = req.perm.has_permission(LIST_APPEND);
         data['author'] = req.authname or ""
-        data['defaults'] = list(self._newrecordings_list(cursor, req))
+        data['defaults'] = list(self._newrecordings_list(req))
         data['weekdaynames'] = translate("**weekdaynames")
         data['statusnames'] = translate("**statusnames")
         data['prioritynames'] = translate("**prioritynames")
-        data['channelnamelist'] = [ "" ] + self._channelname_list(cursor, req)
+        data['channelnamelist'] = [ "" ] + self._channelname_list(req)
         data['_'] = translate
 
         return ('recordinglist.html', data, None)
     """
       Returns list of the adapter settings
     """
-    def _channelname_list(self, cursor, req):
-        return channelname_list(cursor, req)
+    def _channelname_list(self, req):
+        if DvbCronRecordingChannelsPlugin is None:
+            return []
+        channels = DvbCronRecordingChannelsPlugin(self.env)
+        return channels.channelname_list(req)
     
-    def _newrecordings_list(self, cursor, req):
+    def _newrecordings_list(self, req):
         translate = Translate("core", req.locale)
         channel = translate.get("prime-time-channel", "pro7")
         starts = translate.get("prime-time-start", "20:15")
     """
       Returns list of recordings
     """
-    def _recordings_list(self, cursor, req):
-        cols = ['id', 'channelname','newtime','endtime','extratime','weekday','onlydate','status','priority','title']
-        sql = "SELECT %s FROM recording_list" % (",".join(cols))
-        sql += " ORDER BY weekday, newtime, endtime, status"
-        cursor.execute(sql)
-        entries = []
-        for entry in cursor:
-            entry = dict(zip(cols, entry))
-            entries.append(entry)
-        return entries
+    def _recordings_list(self, req):
+        session = db_cnx(self.env)
+        q = session.query(RecordingList)
+        q = q.order_by("weekday", "newtime", "endtime", "status")
+        return q.all()
 
     """
       Appends a new entry into list
     """
-    def _recordings_append(self, cursor, req):
-        cols = ['id', 'channelname','newtime','endtime','extratime','weekday','onlydate','status','priority','title']
-        vals = [] # { "author" : req.authname or 'anonymous' }
-        for col in cols:
-            if req.args.has_key(col):
-                vals += [ aff(req.args[col], "recording_list", col) ]
-            else:
-                vals += [ None ]
-        sql = "INSERT INTO recording_list (%s)" % ",".join(cols)
-        sql += " VALUES (%s)" % (",".join(["%s"] * len(cols)))
-        cursor.execute(sql, vals)
-        return u"OK"
+    def _recordings_append(self, req):
+        item = RecordingList()
+        item.channelname = req.args.get("channelname")
+        item.newtime = req.args.get("newtime")
+        item.endtime = req.args.get("endtime")
+        item.extratime = req.args.get("extratime")
+        item.weekday = req.args.get("weekday")
+        item.onlydate = req.args.get("onlydate")
+        item.status = req.args.get("status")
+        item.priority = req.args.get("priority")
+        item.title = req.args.get("title")
+        session = db_cnx(self.env)
+        session.add(item)
+        session.flush()
+        return u"OK [rec:%s]" % item.id
 
     """
       Updates an entry in the list
     """
-    def _recordings_update(self, cursor, req):
-        cols = ['channelname','newtime','endtime','extratime','weekday','onlydate','status','priority','title']
-        vals = [] # { "author" : req.authname or 'anonymous' }
-        for col in cols:
-            if req.args.has_key(col):
-                vals += [ aff(req.args[col], "recording_list", col) ]
-            else:
-                vals += [ None ] 
-        sqlX = ""
-        try:
-            id = int(req.args["id"])
-            if not id: return "ERROR: no id given"
-            sql = "UPDATE recording_list " 
-            sql += " SET "+(",".join([" %s = %%s" % col for col in cols]))
-            sql += " WHERE id = %s"
-            sqlX = sql + " | " + str( vals + [ id ] )
-            cursor.execute(sql, vals + [ id ])
-        except Exception, e:
-            return u"ERROR: %s\n%s" % (str(e), sqlX)
-        return u"OK"
+    def _recordings_update(self, req):
+        session = db_cnx(self.env)
+        item = session.query(RecordingList).get(req.args.get("id"))
+        if not item:
+            return "ERROR: no recordinglist item with id=%s" % req.args.get("id") 
+        item.channelname = req.args.get("channelname")
+        item.newtime = req.args.get("newtime")
+        item.endtime = req.args.get("endtime")
+        item.extratime = req.args.get("extratime")
+        item.weekday = req.args.get("weekday")
+        item.onlydate = req.args.get("onlydate")
+        item.status = req.args.get("status")
+        item.priority = req.args.get("priority")
+        item.title = req.args.get("title")
+        session.flush()
+        return u"OK [rec:%s]" % item.id
 
     """
       Deletes entry from recordings
     """
-    def _recordings_delete(self, cursor, req):
-        id = int(req.args["id"])
-        cursor.execute("DELETE FROM recording_list WHERE id = %s", [ id ])
+    def _recordings_delete(self, req):
+        q = db_cnx(self.env).query(RecordingList)
+        q = q.filter_by(id = req.args.get("id"))
+        q.delete()
 
 
     # ---------------------------------------------------------------
     # old-style format
     # ---------------------------------------------------------------
     
-    def savetext(self, cursor, req, text):
+    def savetext(self, req, text):
         translate = Translate("core", req.locale)
         msgs = []
         for line in text.split("\n"):
             if line.startswith("+"):
-                msgs += [ self.savetextline(cursor, req, line) ]
+                msgs += [ self.savetextline(req, line) ]
         n = len(msgs)
         msg = translate.nget("(%(n)s line)", "(%(n)s lines)", n) % locals()
         msg += "\n %s" % str(msgs)
         return msg
-    def savetextline(self, cursor, req, line):
+    def savetextline(self, req, line):
         m = re.match(r"[+](\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)[!]", line)
         if m:
             channel = m.group(1)
             else: req.args["onlydate"] = ""
             req.args["title"] = title
             req.args["status"] = u"ok" 
-            return self._recordings_append(cursor, req)
+            return self._recordings_append(req)
         else:
             return "NO."
     def enumweekday(self, name):
         for key, value in Translate.de_weekdaynames.items():
             if value == name: return key
         return name
-    def loadtext(self, cursor, req):
-        return "\n".join(list(self.loadtextlines(cursor, req)))
-    def loadtextlines(self, cursor, req):
-        entries = self._recordings_list(cursor, req)
+    def loadtext(self, req):
+        return "\n".join(list(self.loadtextlines(req)))
+    def loadtextlines(self, req):
+        entries = self._recordings_list(req)
         computer = TracSimpleRecorderComputer()
         plan = computer.plan(entries)
         for group in plan.groups():
                 yield "+%(channel)s %(newtime)s %(end)s %(day)s %(filename)s!" % locals()
             yield "> %s" % xx
         
-    def _loadtextlines(self, cursor, req):
-        entries = self._recordings_list(cursor, req)
+    def _loadtextlines(self, req):
+        entries = self._recordings_list(req)
         startchain = self._entries_to_startchain(entries)
         for chain in startchain:
             entry = chain.entry
                     end = endtime+"+"+extratime
                 else:
                     end = endtime
-                filename = quote_plus_str(check["title"])
+                filename = quote4(check["title"])
                 yield "+%(channel)s %(newtime)s %(end)s %(day)s %(filename)s!" % locals()
             yield "> %s" % xx
 
     # ---------------------------------------------------------------
     # chain up the entries
     # ---------------------------------------------------------------
-    def _recording_plan(self, cursor, req):
-        entries = self._recordings_list(cursor, req)
+    def _recording_plan(self, req):
+        entries = self._recordings_list(req)
         return list(self._recording_plan_from(entries))
     def _recording_plan_from(self, entries):
         computer = TracSimpleRecorderComputer()
         plan = computer.plan(entries)
         for item in plan.items():
             yield item
+            
+    def _channelname_to_channelconflist(self, req, channelname):
+        if DvbCronRecordingChannelsPlugin is None:
+            return []
+        channels = DvbCronRecordingChannelsPlugin(self.env)
+        return channels.channelname_to_channelconflist(req, channelname)
+    def adapter_list(self, req):
+        if DvbCronRecordingChannelsPlugin is None:
+            return [ 0 ]
+        channels = DvbCronRecordingChannelsPlugin(self.env)
+        return channels.adapter_list(req)
+    def channelsconf_for_adapter(self, adapter):
+        if DvbCronRecordingChannelsPlugin is None:
+            return [ 0 ]
+        channels = DvbCronRecordingChannelsPlugin(self.env)
+        return channels.channelsconf_for_adapter(adapter)
+
 
     """ 
         This is doing the actual computations
     """
-    def _makefile_entries(self, cursor, req):
-        entries = self._recordings_list(cursor, req)
+    def _makefile_entries(self, req):
+        entries = self._recordings_list(req)
         computer = TracSimpleRecorderComputer()
         plan = computer.plan(entries)
         for group in plan.groups():
                 adapter = item.tuning_adapter
                 channel = item.tuning_channel
                 if not adapter: adapter = "0"
-                for tuning in channelname_to_channelconflist(cursor, req, channelname):
+                for tuning in self._channelname_to_channelconflist(req, channelname):
                     # TODO: allow to change the adapter
                     if tuning.adapter != adapter: continue
                     if tuning.channel: channel = tuning.channel                
                 frontend = 0
                 if ":" in adapter:
                     adapter, frontend = adapter.split(":",1)
-                if not channel: channel = item.channelname
-                channelsconf = self.channelsconf_filename(cursor, adapter)
+                channel = item.channelname
+                channelsconf = self.cron_channelsconf_filename(adapter)
                 format = "%Y%m%d-"+ustr(adapter)+"%a-%H%M"
                 prefix = self.intodir+"/"
                 cmd = [ u"gnutv -adapter %(adapter)s -frontend %(frontend)s" ]
             make.start = startname 
             yield make
             
-    def _crontablines(self, cursor, req):
-        return self._to_crontablines(self._makefile_entries(cursor, req))
+    def _crontablines(self, req):
+        return self._to_crontablines(self._makefile_entries(req))
     def _to_crontablines(self, makefile_entries):
         for entry in makefile_entries:
             line = ("%s %s make -f %s %s" % (entry.cron, 
         return os.path.join(self.vardir, RECORDING_UPDATE_CRON)
     def cron_update_change_filename(self):
         return os.path.join(self.vardir, RECORDING_UPDATE_CHANGE_CRON)
+    def cron_channelsconf_filename(self, adapter):
+        if not adapter or adapter == "0": adapter = ""
+        return os.path.join(self.vardir, RECORDING_CHANNELSCONF % { "adapter" : str(adapter)})
     def cronminutes_string(self):
         cronminutes = ",".join([ item.strip() 
             for item in self.cronminutes if item.strip() ])
     def cron_target_filename(self):
         cron_update_filename = self.cron_update_filename()
         return os.path.join("/etc/cron.d", os.path.basename(cron_update_filename))
-    def channelsconf_filename(self, cursor, adapter):
-        channelsconf = self.channelsconf
-        if DvbCronRecordingTuningPlugin is not None:
-            tuning = DvbCronRecordingTuningPlugin(self.compmgr)
-            found = tuning.channelsconf_from_adapter(cursor, adapter)
-            if found: channelsconf = found
-        user = self.recordinguser
-        channelsconf = channelsconf.replace("~/", "~{user}/")
-        channelsconf = channelsconf.replace("{recordinguser}", "{user}")
-        channelsconf = channelsconf.replace("/home/{user}/", "~{user}/")
-        return os.path.expanduser(channelsconf.format(**locals()))
     def minutes(self, spec):
         m = re.match("(\d+):(\d+)", spec)
         if m: return int(m.group(1))*60 + int(m.group(2))

File src/dvbcronrecording/db/db4.py

+# http://trac-hacks.org/wiki/TracSqlAlchemyBridgeIntegration
+
+from sqlalchemy import (
+    # MetaData,
+    # Boolean,
+    Column,
+    DateTime,
+    # ForeignKey,
+    Integer,
+    String,
+    # Unicode,
+    )
+#from sqlalchemy.orm import relationship, backref
+#from sqlalchemy.orm.exc import NoResultFound
+
+from sqlalchemy.ext.declarative import declarative_base
+RecordingModel = declarative_base()
+
+class RecordingList(RecordingModel):
+    __tablename__ = "recording_list"
+    id = Column('id', Integer, primary_key=True)
+    channelname = Column('channelname', String)
+    newtime = Column('newtime', String)
+    endtime = Column('endtime', String)
+    extratime = Column('extratime', String)
+    onlydate = Column('onlydate', String)
+    title = Column('title', String)
+    status = Column('status', String)
+    weekday = Column('weekday', Integer)
+    priority = Column('priority', Integer)
+
+class RecordingChannels(RecordingModel):
+    __tablename__ = 'recording_channels'
+    id = Column('id', Integer, primary_key=True)
+    channelname = Column('channelname', String)
+    adapter = Column('adapter', String)
+    title = Column('title', String)
+
+class RecordingChannelsConf(RecordingModel):
+    __tablename__ = 'recording_channelsconf'
+    adapter = Column('adapter', String, primary_key = True)
+    title = Column('title', String, primary_key = True)
+    frequency = Column('frequency', Integer)
+    polarity = Column('polarity', String)
+    source = Column('source', String)
+    symbolrate = Column('symbolrate', Integer)
+    vpid = Column('vpid', Integer)
+    apid = Column('apid', Integer)
+    tpid = Column('tpid', Integer)
+
+class RecordingTuning(RecordingModel):
+    __tablename__ = 'recording_tuning'
+    id = Column("id", Integer, primary_key=True)
+    adapter = Column('adapter', String)
+    satellite = Column('satellite', String)
+    transponder = Column('transponder', String)
+    scansettings = Column('scansettings', String)
+    scansourcefile = Column('scansourcefile', String)
+    channelsconf = Column('channelsconf', String)
+
+class RecordingChanges(RecordingModel):
+    __tablename__ = 'recording_changes'
+    id = Column("id", Integer, primary_key=True)
+    tablename = Column("tablename", String)
+    changed = Column("changed", String)
+    username = Column("username", String)
+    modified = Column("modified", DateTime)
+
+metadata = RecordingModel.metadata
+
+from tsab import engine
+
+def create_all(env, cursor):
+    metadata.create_all(bind=engine(env))
+
+def upgrade(env, cursor):
+    import schemachange
+    try:
+        for sql in schemachange.migrate_AtoB(old_metadata(), metadata, env, excludeTables = None):
+            print sql
+    except Exception, e:
+        import traceback
+        traceback.print_exc(e)
+        raise
+
+def downgrade(env, cursor):
+    import schemachange
+    for sql in schemachange.migrate_BtoA(old_metadata(), metadata, env, excludeTables = None):
+        print sql
+
+def old_metadata():
+    import db3
+    return db3.metadata

File src/dvbcronrecording/db/schema.py

 
-import db1 
+import db4 
 
-tables = db1.tables
+metadata = db4.metadata
+tables = metadata.sorted_tables # support old aff() detection
 
+RecordingTuning = db4.RecordingTuning
+RecordingChannels = db4.RecordingChannels
+RecordingChannelsConf = db4.RecordingChannelsConf
+RecordingList = db4.RecordingList

File src/dvbcronrecording/db/session.py

+from tsab import engine
+from sqlalchemy.orm import create_session
+
+_session = None
+
+def context(env):
+    global _session
+    if _session is None:
+        _session = create_session(bind=engine(env))
+    return _session
+
+def db_cnx(env): return context(env)
+
+def commit():
+    global _session
+    if _session is not None:
+        _session.flush()
+
+def close():
+    global _session
+    if _session is not None:
+        _session.close()
+        _session = None
+            

File src/dvbcronrecording/db/version.py

+number = 4

File src/dvbcronrecording/files.py

         req.perm.assert_permission('DVBREC_LIST_VIEW')
         translate = Translate("files", req.locale)
 
-        # getting cursor
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
         page = req.args['page']
         if not page: page = "list"
         videotype="video/mpeg"
             name = page[len("play/"):]
             data = {}
             data['title'] = translate("Show Video")
-            data['entries'] = self._file_entries(cursor, req)
+            data['entries'] = self._file_entries(req)
             data['videofile'] = name
             data['videoheight'] = 576
             data['videowidth'] = 768
             diskfree = commands.getoutput("df -h "+self.intodir)
             message =  "| \n".join([ item for item in diskfree.split("\n") if "/" in item ])
             # message = diskfree 
-        entries = self._file_entries(cursor, req)
+        entries = self._file_entries(req)
         data = {}
         data['title'] = translate("Recorded Files Overview")
         data["_pagenum"] = req.args.get("_pagenum", "0")
         add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
         add_script(req, 'common/js/trac.js')
         return ('recordingfiles.html', data, None)
-    def _file_entries(self, cursor, req):
+    def _file_entries(self, req):
         if False:
             entry = FileEntry()
             entry.name = self.intodir+"/*.mpg"

File src/dvbcronrecording/init.py

 
 from trac.core import Component, implements
 from trac.env import IEnvironmentSetupParticipant
+import db.version
 
 # Last guestbooks database schema version
-last_db_version = 3
+last_db_version = db.version.number
 name_db_version = 'dvbcronrecording_version'
 name_db_version_old = 'simplerecorder_version'
 PACKAGE = "dvbcronrecording"

File src/dvbcronrecording/templates/channels_list.html

     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml"
-      xmlns:py="http://genshi.edgewall.org/"
-      xmlns:xi="http://www.w3.org/2001/XInclude">
-  <xi:include href="layout.html" />
-  <xi:include href="macros.html" />
-<head><title>${title}</title></head>
+	xmlns:py="http://genshi.edgewall.org/"
+	xmlns:xi="http://www.w3.org/2001/XInclude">
+<xi:include href="layout.html" />
+<xi:include href="macros.html" />
+<head>
+<title>${title}</title>
+</head>
 <body>
-<div id="ctxtnav" class="nav">
-</div>
+	<div id="ctxtnav" class="nav"></div>
 
-<div id="content" class="channelslist">
-  <h1>
-    ${title}
-  </h1>
-  <xi:include href="channels_nav.html" />
-  <xi:include href="channelsconf_nav.html" />
-  <div>${message}</div><br />
+	<div id="content" class="channelslist">
+		<h1>${title}</h1>
+		<xi:include href="channels_nav.html" />
+		<xi:include href="channelsconf_nav.html" />
+                <div>
+                        <!--  -->
+                        &nbsp; &nbsp; &nbsp; &nbsp;
+                        <form method="post" action="${href.recording('channels/text')}"
+                                class="actionform">
+                                <select name="adapter">
+                                        <option py:for="adaptername in sorted(adapterlist)" 
+                                           value="${adaptername}"
+                                           selected="${adaptername == adapter or None}">
+                                                ${adaptername}</option>
+                                </select>
+                                <input type="submit" value="${_('channels.conf text')}"
+                                        class="actionbutton"></input>
+                        </form>
+                </div>
+		
+		
+		<div>${message}</div>
+		<br />
 
-  <table cellpadding="0" cellspacing="0">
-        <tr>
-      <th>x</th>
-      <th class="id">#</th>
-      <th class="channelname">${_('channelname')}&nbsp;</th>
-      <th class="adapter">${_('adapter')}&nbsp;</th>
-      <th class="title">${_('channel.conf title')}&nbsp;</th>
-      <th>${_('action buttons')}</th>
-      </tr>
-      
-    <py:if test="perm.has_permission('DVBREC_CHANNELS_EDIT')">
-  <div py:for="item in new_datalist">
-    <form method="post" action="${href.recording('channels/newentry')}">
-      <tr>
-      <td class="id new" colspan="2">${_('NEW')}</td>
-      <td class="channelname"><input type="text" name="channelname" value="${item.channelname}"></input></td>
-      <td class="adapter">
-            <select name="adapter">
-                <option py:for="adapter in sorted(adapterlist)" 
-                        value="${adapter}">
-                        ${adapter}
-                </option>
-            </select>
-      </td>
-      <td class="title"><input type="text" name="title" value="${item.title}"></input></td>
-      <td class="action"><input type="submit" value="${_('insert')}"></input></td>
-      </tr>
-     </form>    
-  </div>    
-    </py:if>
+		<table cellpadding="0" cellspacing="0">
+			<tr>
+				<th>x</th>
+				<th class="id">#</th>
+				<th class="channelname">${_('channelname')}&nbsp;</th>
+				<th class="adapter">${_('adapter')}&nbsp;</th>
+				<th class="title">${_('channel.conf title')}&nbsp;</th>
+				<th>${_('action buttons')}</th>
+			</tr>
 
-  <div py:for="item in datalist">
-  <form method="post" action="${href.recording('channels/update')}">
-    <tr class="status${item.status}">
-      <td>
-        <py:if test="perm.has_permission('DVBREC_CHANNELS_EDIT')">
-          <span  class="deletebutton">
-          <a href="${href.recording('channels/delete')}?id=${item.id}">
-                   &#x2716;
-          </a>
-          </span>
-        </py:if>
-      </td>
-      <td class="id">
-          ${item.id}<input type="hidden" name="id" value="${item.id}"></input>
-      </td>
-      <td>
-         <input type="text" name="channelname" value="${item.channelname}" 
-             style="width: 6em" /></td>
-      <td class="adapter">
-            <select name="adapter">
-                <option py:for="adapter in sorted(adapterlist)" 
-                        value="${adapter}">
-                        ${adapter}
-                </option>
-            </select>
-      </td>
-      <td class="title"><input type="text" name="title" value="${item.title}"></input></td>
-      <td class="action">
-        <py:if test="perm.has_permission('DVBREC_CHANNELS_EDIT')">
-          <input type="submit" value="${_('update')}" class="actionbutton" />
-        </py:if>
-      </td>
-    </tr>
-    </form>
-  </div>
-  </table>
-  <xi:include href="paginator_datalist.html" />
-</div>
-<hr />
-</body></html>
+			<py:if test="perm.has_permission('DVBREC_CHANNELS_EDIT')">
+				<div py:for="item in new_datalist">
+					<form method="post" action="${href.recording('channels/newentry')}">
+						<tr>
+							<td class="id new" colspan="2">${_('NEW')}</td>
+							<td class="channelname"><input type="text"
+								name="channelname" value="${item.channelname}"></input>
+							</td>
+							<td class="adapter"><select name="adapter">
+									<option py:for="adapter in sorted(adapterlist)"
+										value="${adapter}">${adapter}</option>
+							</select></td>
+							<td class="title"><input type="text" name="title"
+								value="${item.title}"></input>
+							</td>
+							<td class="action"><input type="submit"
+								value="${_('insert')}"></input>
+							</td>
+						</tr>
+					</form>
+				</div>
+			</py:if>
+
+			<div py:for="item in datalist">
+				<form method="post" action="${href.recording('channels/update')}">
+					<tr class="status${item.status}">
+						<td><py:if test="perm.has_permission('DVBREC_CHANNELS_EDIT')">
+								<span class="deletebutton"> <a
+									href="${href.recording('channels/delete')}?id=${item.id}">
+										&#x2716; </a> </span>
+							</py:if></td>
+						<td class="id">${item.id}<input type="hidden" name="id"
+							value="${item.id}"></input></td>
+						<td><input type="text" name="channelname"
+							value="${item.channelname}" style="width: 6em" />
+						</td>
+						<td class="adapter"><select name="adapter">
+								<option py:for="adapter in sorted(adapterlist)"
+									value="${adapter}">${adapter}</option>
+						</select></td>
+						<td class="title"><input type="text" name="title"
+							value="${item.title}"></input>
+						</td>
+						<td class="action"><py:if
+								test="perm.has_permission('DVBREC_CHANNELS_EDIT')">
+								<input type="submit" value="${_('update')}" class="actionbutton" />
+							</py:if></td>
+					</tr>
+				</form>
+			</div>
+		</table>
+		<xi:include href="paginator_datalist.html" />
+		<div>
+			<!--  -->
+			&nbsp; &nbsp; &nbsp; &nbsp;
+			<form method="post" action="${href.recording('channels/text')}"
+				class="actionform">
+				<select name="adapter">
+					<option py:for="adapter in sorted(adapterlist)" value="${adapter}">
+						${adapter}</option>
+				</select>
+				<input type="submit" value="${_('channels.conf text')}"
+					class="actionbutton"></input>
+			</form>
+		</div>
+	</div>
+
+	<hr />
+</body>
+</html>

File src/dvbcronrecording/templates/channels_text.html

+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+	xmlns:py="http://genshi.edgewall.org/"
+	xmlns:xi="http://www.w3.org/2001/XInclude">
+<xi:include href="layout.html" />
+<xi:include href="macros.html" />
+<head>
+<title>${title}</title>
+</head>
+<body>
+	<div id="ctxtnav" class="nav"></div>
+	<div id="content" class="channelslist">
+		<h1>${title}</h1>
+		<xi:include href="channels_nav.html" />
+		<xi:include href="channelsconf_nav.html" />
+		<div>
+			<!--  -->
+			&nbsp; &nbsp; &nbsp; &nbsp;
+			<form method="post" action="${href.recording('channels/text')}"
+				class="actionform">
+				<select name="adapter">
+					<option py:for="adaptername in sorted(adapterlist)"
+						value="${adaptername}"
+						selected="${adaptername == adapter or None}">
+						${adaptername}</option>
+				</select>
+				<input type="submit" value="${_('channels.conf text')}"
+					class="actionbutton"></input>
+			</form>
+		</div>
+
+
+
+		<div>${message}</div>
+		<br />
+		<form>
+			<textarea cols="80" rows="${rows or 10}">${text}</textarea>
+		</form>
+	</div>
+
+	<hr />
+</body>
+</html>

File src/dvbcronrecording/templates/tuninglist.html

       </td>      
       </tr><tr>
       <td align="right" colspan="2">&nbsp;&#x21AA;&nbsp;</td>
-      <td><label for="channelsconf">${_('using')}</label></td>
+      <td><label for="channelsconf">${_('related')}</label></td>
       <td class="channelsconf"><input type="text" name="channelsconf" value="${item.channelsconf}"></input></td>
-      <td class="extra"> &nbsp;  ${_('as the channels.conf')}
+      <td class="extra"> &nbsp;  ${_('as the original channels.conf')}
       </td>
       </tr>
      </form>         
       </td>
       </tr><tr>
       <td align="right" colspan="2">&nbsp;&#x21AA;&nbsp;</td>
-      <td><label for="channelsconf">${_('using')}</label></td>
+      <td><label for="channelsconf">${_('related')}</label></td>
       <td class="channelsconf"><input type="text" name="channelsconf" value="${item.channelsconf}"></input></td>
-      <td class="extra"> &nbsp; ${_('as the channels.conf')}
+      <td class="extra"> &nbsp; ${_('as the original channels.conf')}
       </td>
     </tr>
     </form>

File src/dvbcronrecording/tuning.de.po

 msgid "from"
 msgstr "aus"
 
-msgid "using"
-msgstr "verwendet"
+msgid "related"
+msgstr "verknüpft"
 
-msgid "as the channels.conf"
-msgstr "als channels.conf"
+msgid "as the original channels.conf"
+msgstr "als ursprünglicher channels.conf"
 
 msgid "input 0:0 for <adapter>:<frontend>"
 msgstr "Eingabe 0:0 meint <Adapter>:<Frontend>"

File src/dvbcronrecording/tuning.py

 from trac.web import IRequestHandler
 from trac.perm import IPermissionRequestor
 from trac.web.chrome import  ITemplateProvider # INavigationContributor,
-from db import schema
+from db.session import db_cnx #@UnresolvedImport
+from db.schema import RecordingTuning
+import sqlalchemy as _as
 
 from translate import Translate #@UnresolvedImport
 
 
-def affinity(table_name, column_name):
-    for table in schema.tables:
-        if table.name == table_name:
-            for column in table.columns:
-                if column.name == column_name:
-                    return column.type
-    return None
-def aff(value, table_name, column_name):
-    if value is None: return None
-    typed = affinity(table_name, column_name)
-    if typed in [ "int", "INT", "integer", "INTEGER"]:
-        return intnull(value)
-    return ustr(value)
 def intnull(value, default = None):
     if value is None: return default
     try: return int(value)
     def process_request(self, req):
         req.perm.assert_permission(TUNING_VIEW)
 
-        # getting cursor
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
         page = req.args['page']
         if not page: page = "list"
         
         if page in [ None, '', 'list' ]:
             pass
         if page in [ 'newentry', 'append' ]:
-            message = self._tuning_append(cursor, req)
+            message = self._tuning_append(req)
         if page in [ 'update' ]:
-            message = self._tuning_update(cursor, req)
+            message = self._tuning_update(req)
         if page in [ 'delete' ]:
-            message = self._tuning_delete(cursor, req)
+            message = self._tuning_delete(req)
         
         # database commit and return page content
-        db.commit()
+        db_cnx(self.env).flush()
 
         add_stylesheet(req, 'common/css/wiki.css')
         add_stylesheet(req, PACKAGE+'/css/tuninglist.css')
         data = {}
         data['message'] = message
         data['title'] = translate('Tuning List')
-        data['newtunings'] = self._newtuning_list(cursor, req)
-        data['tunings'] = self._tuning_list(cursor, req)
+        data['newtunings'] = self._newtuning_list(req)
+        data['tunings'] = self._tuning_list()
         data['appends'] = req.perm.has_permission(TUNING_VIEW);
         data['author'] = req.authname or ""
         data['_'] = translate