Commits

Guido Draheim  committed 9e2c683 Merge

Merge with trunk

  • Participants
  • Parent commits 7ad0e9a, f4401b0
  • Branches dvbcronrecording
  • Tags dvbcronrecording-0.4.16

Comments (0)

Files changed (43)

 syntax: regexp
 ^build$
 syntax: regexp
-^rpm$
+^rpm$
+syntax: regexp
+^src/dvbcronrecording/locale/de/LC_MESSAGES/dvbcronrecording\.po$
+syntax: regexp
+^src/MANIFEST$

File .pydevproject

 <?eclipse-pydev version="1.0"?>
 
 <pydev_project>
-<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">python2.7</pydev_property>
 <pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
 <pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
 <path>/DvbCronRecording/src</path>
-<path>/DvbCronRecording/src/tracsimplerecorder</path>
 </pydev_pathproperty>
 <pydev_pathproperty name="org.python.pydev.PROJECT_EXTERNAL_SOURCE_PATH">
 <path>/usr/lib/python2.6/site-packages</path>
-<path>/usr/lib64/python2.6</path>
-<path>/usr/lib64/python2.6/site-packages</path>
+<path>/usr/lib/python2.7/site-packages</path>
+<path>/usr/lib64/python2.7</path>
+<path>/usr/lib64/python2.7/site-packages</path>
 </pydev_pathproperty>
 </pydev_project>

File .settings/org.eclipse.core.resources.prefs

-#Sun Jul 10 11:46:17 CEST 2011
+#Fri Sep 02 12:38:21 CEST 2011
 eclipse.preferences.version=1
 encoding//src/dvbcronrecording/channels.py=utf-8
 encoding//src/dvbcronrecording/channelsconf.py=utf-8
 encoding//src/dvbcronrecording/core.py=utf-8
 encoding//src/dvbcronrecording/db/tsab.py=utf-8
+encoding//src/dvbcronrecording/db/tsab2.py=utf-8
 encoding//src/dvbcronrecording/init.py=utf-8
 encoding//src/dvbcronrecording/tuning.py=utf-8
 encoding//src/setup.py=utf8

File src/GNUmakefile

 upd up: rpm update restart
 
 msgfmt:
-	for po in $(PKG)/*.po; do : \
-	; name=`basename "$$po" | sed -e "s|.po\$$||"` \
-	; lang=`echo "$$name" | sed -e "s|.*[.]||"` \
-	; file=`echo "$$name" | sed -e "s|[.][^.]*$$||"` \
-	; messages=$(PKG)/messages.$$lang.po \
-	; [ -s "$$messages" ] || messages="" \
-	; [ "$$file" = "messages" ] && messages="" \
-	; dirpath=$(PKG)/locale/$$lang/LC_MESSAGES \
-	; test -d $$dirpath || mkdir -p $$dirpath \
-	; echo msgfmt $$messages $$po -o $$dirpath/$$file.mo \
-	; msgfmt $$messages $$po -o $$dirpath/$$file.mo || exit 1 \
-	; done
+	python setup.py copy_catalog
+	python setup.py compile_catalog
 	
+# builds: msgfmt
 builds: msgfmt
 	: python setup.py --help build 
 	python setup.py build --build-base=$(BUILDDIR)
                      '--define=_builddir $(BUILDDIR)' \
                 $(RPMBUILDOPTIONS)
 
-# setuptools sdist is broken for package_data in 0.6c11 vs python 2.6.5	
+# setuptools sdist is broken for package_data in 0.6c11 vs python 2.6.5, known bug!
+# It does simply not work - so use "make dist" instead of "setup.py sdist"
+sdist:
+	test -d $(RPMROOT)/SOURCES || mkdir -p $(RPMROOT)/SOURCES
+	python setup.py sdist --dist-dir $(RPMROOT)/SOURCES -v -v 
 dist: msgfmt
 	: python setup.py sdist --build-base=$(BUILDDIR) \
 	   --dist-dir SOURCES -v -v $X
 	test -d $(RPMROOT)/SOURCES || mkdir -p $(RPMROOT)/SOURCES
-	tar czvf $(RPMROOT)/SOURCES/$(PKG).tgz *.py *.txt *.spec $(PKG)/
+	tar czvf $(RPMROOT)/SOURCES/$(PKG).tgz *.py *.cfg *.txt *.spec $(PKG)/
 	ls -l $(RPMROOT)/SOURCES/$(PKG).tgz
 	@ version=`cat $(SPECNAME).spec | sed -e '/define _version/!d' -e 's/.*_version //'` \
 	; echo mv $(RPMROOT)/SOURCES/$(PKG).tgz $(RPMROOT)/SOURCES/$(PKG)-$$version.tgz \

File src/dvbcronrecording/channels.de.po

-msgid ""
-msgstr ""
-"Content-Type: text/plain; charset=UTF-8\n"
-"Report-Msgid-Bugs-To: guidod@gmx.de\n"
-"Plural-Forms: nplurals=3; plural=(n==0 ? 0 : (n == 1 ? 1 : 2));\n"
-
-msgid "insert"
-msgstr "einfügen"
-
-msgid "delete"
-msgstr "[loeschen]"
-
-msgid "update"
-msgstr "aktualisieren"
-
-msgid "play"
-msgstr "Abspielen"
-
-msgid "mplay"
-msgstr "mit MPlayer"
-
-
-msgid "adapter"
-msgstr "Adapter"
-
-msgid "channel.conf title"
-msgstr "Bezeichnung in der channel.conf"
-
-msgid "channel.conf show"
-msgstr "channel.conf Ansicht"
-
-msgid "channel.conf text"
-msgstr "channel.conf Text"
-
-msgid "action buttons"
-msgstr "Betriebsschalter"
-
-msgid "status"
-msgstr "Status"
-
-msgid "NEW"
-msgstr "NEU"
-
-msgid "SAVE"
-msgstr "SPEICHERN"
-
-msgid "newchannel"
-msgstr "neusender"
-
-msgid "existing channel.conf entry"
-msgstr "existierender channel.conf Eintrag"
-
-msgid "please do not use umlauts"
-msgstr "Bitte keine Umlaute verwenden"
-
-msgid "-default-channels"
-msgstr "3sat,arte"

File src/dvbcronrecording/channels.py

     """
     def process_request(self, req):
         req.perm.assert_permission(CHANNELS_VIEW)
-        translate = Translate("channels", req.locale)
+        translate = Translate(PACKAGE, req.locale)
 
         page = req.args['page']
         if not page: page = "list"
         if not message: message = translate("please do not use umlauts")
         message += translate("?problem")
         newchannel_list = self._newchannel_list(req)
-        channel_list = self._channel_list(req.args)
+        channel_list = self._channel_list(args)
 
         # passing variables to template
         data = {}
         data['_'] = translate
         add_stylesheet(req, 'common/css/wiki.css')
         add_stylesheet(req, PACKAGE+'/css/recordingchannels.css')
+        add_stylesheet(req, PACKAGE+'/css/dvbcronrecording.css')
         add_script(req, 'common/js/trac.js')
         add_script(req, 'common/js/wikitoolbar.js')
         return ('channels_list.html', data, None)
 
     def show_text(self, req, page):
         # passing variables to template
-        translate = Translate("channels", req.locale)
+        translate = Translate(PACKAGE, req.locale)
         adapter = req.args["adapter"]
         conf = self.channelsconf_for_adapter(adapter)
         data = {}
         data['_'] = translate
         add_stylesheet(req, 'common/css/wiki.css')
         add_stylesheet(req, PACKAGE+'/css/recordingchannels.css')
+        add_stylesheet(req, PACKAGE+'/css/dvbcronrecording.css')
         add_script(req, 'common/js/trac.js')
         add_script(req, 'common/js/wikitoolbar.js')
         return ('channels_text.html', data, None)
     
 
     def _newchannel_list(self, req):
-        translate = Translate("channels", req.locale)
+        translate = Translate(PACKAGE, req.locale)
         _ = ['channelname', 'adapter', 'title']
         defs = {}
         defs["channelname"] = translate("newchannel")
         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")
+        old = "%s:%s:%s" % (item.channelname, item.adapter, item.title)
         item.channelname = req.args.get("channelname")
         item.adapter = req.args.get("adapter")
         item.title = req.args.get("title")
+        new = "%s:%s:%s" % (item.channelname, item.adapter, item.title)
         session.flush()
-        return "OK"
+        return "OK(%s -> %s)" % (old, new)
 
     """
       Deletes entry from recordings
         """ Returns list of the adapter settings """
         session = db_cnx(self.env)
         if defaults is None:
-            translate = Translate("channels", req.locale)
+            translate = Translate(PACKAGE, req.locale)
             _defaults = translate.get("-default-channels", "3sat")
             defaults = [ item.strip() for item in _defaults.split(",") ]
         cols = ['channelname']
                              str(item.apid),
                              str(item.tpid),                             
                              ])
+    def channels_list(self):
+        " currently it returns directly the db orm items"
+        return self._channel_list()
 
 class RecordingChannelsItem: pass
 class RecordingChannelsConfItem: pass

File src/dvbcronrecording/channelsconf.de.po

-msgid ""
-msgstr ""
-"Content-Type: text/plain; charset=UTF-8\n"
-"Report-Msgid-Bugs-To: guidod@gmx.de\n"
-"Plural-Forms: nplurals=3; plural=(n==0 ? 0 : (n == 1 ? 1 : 2));\n"
-
-msgid "insert"
-msgstr "einfügen"
-
-msgid "delete"
-msgstr "löchen"
-
-msgid "update"
-msgstr "aktualisieren"
-
-msgid "takeover"
-msgstr "übernehmen"
-
-msgid "play"
-msgstr "Abspielen"
-
-msgid "mplay"
-msgstr "mit MPlayer"
-
-msgid "channel.conf title"
-msgstr "Bezeichnung in der channel.conf"
-
-msgid "channel.conf show"
-msgstr "channel.conf Ansicht"
-
-msgid "action"
-msgstr "vorgehen"
-
-msgid "adapter"
-msgstr "Adapter"
-
-msgid "frequency"
-msgstr "Frequenz"
-
-msgid "polarity"
-msgstr "Polarität"
-
-msgid "source"
-msgstr "Quelle"
-
-msgid "symbolrate"
-msgstr "SymbolRate"
-
-msgid "vpid"
-msgstr "V-pid"
-
-msgid "apid"
-msgstr "A-pid"
-
-msgid "tpid"
-msgstr "T-pid"
-
-msgid "action buttons"
-msgstr "Betriebsschalter"
-
-msgid "status"
-msgstr "Status"
-
-msgid "NEW"
-msgstr "NEU"
-
-msgid "SAVE"
-msgstr "SPEICHERN"
-
-msgid "please do not use umlauts"
-msgstr "Bitte keine Umlaute verwenden"
-
-msgid "use the traditional dvbscan/zap syntax"
-msgstr "Nutze die uebliche dvbscan/zap Syntax"
-
-msgid "have NOT checked it!"
-msgstr "wurde NICHT überprüft!"
-
-msgid "have checked it!"
-msgstr "wurde überprüft!"
-
-msgid "-new-channelsconf"
-msgstr "NEW CHANNEL"
-
-msgid "-new-frequency"
-msgstr "12000"
-
-msgid "-new-polarity"
-msgstr "H"
-
-msgid "-new-symbolrate"
-msgstr "27500"
-
-msgid "Saved"
-msgstr "Abgespeichert"
-
-msgid "Edit"
-msgstr "Editor"
-
-msgid "Takeover"
-msgstr "Übernommen"
-

File src/dvbcronrecording/channelsconf.py

     # Public methods
     #
 
-    implements(IPermissionRequestor, ITemplateProvider,  IRequestHandler)
+    implements(IPermissionRequestor, ITemplateProvider, IRequestHandler)
 
     # IPermissionRequestor methods
 
     """
     def process_request(self, req):
         req.perm.assert_permission(CHANNELSCONF_VIEW)
-        translate = Translate("channelsconf", req.locale)
+        translate = Translate(PACKAGE, req.locale)
 
         # getting cursor
         page = req.args['page']
             data = {}
             data['title'] = translate("Saved")
             data['messages'] = ['(%s)' % text, result]
+            data["adapter"] = req.args.get("adapter", None)
             data['adapterlist'] = self._adapter_list(req)
             data['text'] = self.loadtext(req)
             data['_'] = translate
-            add_stylesheet(req, PACKAGE+'/css/recordingchannelsconf.css')
+            add_stylesheet(req, PACKAGE + '/css/recordingchannelsconf.css')
+            add_stylesheet(req, PACKAGE + '/css/dvbcronrecording.css')
             add_script(req, 'common/js/trac.js')
             return ('channelsconf_editor.html', data, None)
         if page in [ "edit" ]:
             data = {}
             data['title'] = translate("Edit")
             data['messages'] = [ translate('use the traditional dvbscan/zap syntax') ]
+            data["adapter"] = req.args.get("adapter", None)
             data['adapterlist'] = self._adapter_list(req)
             data['text'] = self.loadtext(req)
             data['_'] = translate
-            add_stylesheet(req, PACKAGE+'/css/recordingchannelsconf.css')
+            add_stylesheet(req, PACKAGE + '/css/recordingchannelsconf.css')
+            add_stylesheet(req, PACKAGE + '/css/dvbcronrecording.css')
             add_script(req, 'common/js/trac.js')
             return ('channelsconf_editor.html', data, None)
         if page in [ "take", "takeover" ]:
             data = {}
             data['title'] = translate("Takeover")
             data['messages'] = [ "" ]
+            data["adapter"] = req.args.get("adapter", None)
             data['adapterlist'] = self._adapter_list(req)
+            data['polaritylist'] = polaritylist
             data["_pagenum"] = req.args.get("_pagenum", "0")
             data["_pagesize"] = req.args.get("_pagesize", "10")
             data['datalist'] = Paginator(channels, int(data["_pagenum"]), int(data["_pagesize"]))
             data['_'] = translate
             data['urlencode'] = urlencode
             data['q'] = q
-            add_stylesheet(req, PACKAGE+'/css/recordingchannelsconf.css')
+            add_stylesheet(req, PACKAGE + '/css/recordingchannelsconf.css')
+            add_stylesheet(req, PACKAGE + '/css/dvbcronrecording.css')
             add_script(req, 'common/js/trac.js')
             return ('channelsconf_take.html', data, None)
         
         commit()
 
         add_stylesheet(req, 'common/css/wiki.css')
-        add_stylesheet(req, PACKAGE+'/css/recordingchannelsconf.css')
+        add_stylesheet(req, PACKAGE + '/css/recordingchannelsconf.css')
+        add_stylesheet(req, PACKAGE + '/css/dvbcronrecording.css')
         add_script(req, 'common/js/trac.js')
         add_script(req, 'common/js/wikitoolbar.js')
         
         newchannels_list = self._newchannels_list(req)
         channels_list = self._channelsconf_list(req, channeltype)
         channeltypelist = ["all channel types",
-                           "data channels", 
-                           "radio channels", 
+                           "data channels",
+                           "radio channels",
                            "video channels"]
 
         # passing variables to template
         data['message'] = message
         data['title'] = translate('Channel List')
         data['polaritylist'] = polaritylist
+        data["adapter"] = req.args.get("adapter", None)
         data['adapterlist'] = self._adapter_list(req)
         data["channeltype"] = channeltype
         data["channeltypelist"] = channeltypelist
         data['new_datalist'] = newchannels_list
-        data['_selection'] = "channeltype="+quote2(channeltype)
+        data['_selection'] = "channeltype=" + quote2(channeltype)
         data["_pagenum"] = req.args.get("_pagenum", "0")
         data["_pagesize"] = req.args.get("_pagesize", "10")
         data['datalist'] = Paginator(channels_list, int(data["_pagenum"]), int(data["_pagesize"]))
     """
       Returns list of the adapter settings
     """
-    def _adapter_list(self, req, defaults = [ 0 ]):
+    def _adapter_list(self, req, defaults=[ 0 ]):
         return tuning_adapter_list(self.env, defaults)
     
     """
       Returns list of the channels.conf
     """
-    def _channelsconf_selectby(self, req, selectby = ['adapter', 'title']):
+    def _channelsconf_selectby(self, req, selectby=['adapter', 'title']):
         session = db_cnx(self.env)
         q = session.query(RecordingChannelsConf)
         for col in selectby:
         return q.all()
     
     def _newchannels_list(self, req):
-        _ = ['adapter', 'title','frequency','polarity','source','symbolrate','vpid', 'apid', 'tpid']
-        translate = Translate("channelsconf", req.locale)
+        _ = ['adapter', 'title', 'frequency', 'polarity', 'source', 'symbolrate', 'vpid', 'apid', 'tpid']
+        translate = Translate(PACKAGE, req.locale)
         defs = {}
-        defs["adapter"] = "0"
+        defs["adapter"] = req.args.get("adapter", "0")
         defs["title"] = translate.get("-new-channel", "NEW CHANNEL")
         defs["frequency"] = translate.get("-new-frequency", "12000")
         defs["polarity"] = translate.get("-new-polarity", "H")
     """
       Returns list of the channels.conf
     """
-    def _channelsconf_list(self, 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")
     """
       Appends a new entry into channels.conf list
     """
-    def _channelsconf_append(self, req):
+    def _channelsconf_append(self, req, session=None):
         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"
+        item.adapter = req.args.get("adapter", "0")
+        item.frequency = req.args.get("frequency")
+        item.polarity = req.args.get("polarity")
+        item.source = req.args.get("source")
+        item.symbolrate = req.args.get("symbolrate")
+        item.vpid = req.args.get("vpid")
+        item.apid = req.args.get("apid")
+        item.tpid = req.args.get("tpid")
+        item.title = req.args.get("newtitle", req.args.get("title"))
+        value = "%s:%s" % (item.adapter, item.title)
+        if session:
+            session.add(item)
+        else:
+            session = db_cnx(self.env)
+            session.add(item)
+            session.flush() 
+        return "OK(%s)" % value
 
     """
       Updates an entry in the channels.conf list
 
     # ---------------------------------------------------------------
     def savetext(self, req, text):
-        if not req.args.has_key("adapter"):
+        if not req.args.get("adapter"):
             return "no adapter given in update"
         session = db_cnx(self.env)
         adapter = req.args["adapter"]
-        sql = "DELETE FROM recording_channelsconf WHERE adapter = %s"
-        session.execute(sql, adapter)
+        q = session.query(RecordingChannelsConf).filter_by(adapter=adapter)
+        q.delete()
+        session.flush()
         msg = []
         for line in text.split("\n"):
-            msg += [ self.savetextline(req, unicode(line)) ]
+            textline = unicode(line.strip())
+            if textline:
+                msg += [ self.savetextline(req, textline, session) ]
+        session.flush()
         return "(%s lines = %s)" % (len(msg), str(msg))
-    def savetextline(self, req, line):
-        cols = ["title", "frequency", "polarity", "source", "symbolrate", "vpid","apid","tpid"]
+    def savetextline(self, req, line, session=None):
+        cols = ["title", "frequency", "polarity", "source", "symbolrate", "vpid", "apid", "tpid"]
         # VIVA PLUS:12551:v:0:22000:171:172:12120
-        m = re.match(r"([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+)", line)
+        m = re.match(r"([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+):([^:]+)\s*", line)
         if m:
             vals = m.groups()
             for n in xrange(len(cols)):
                     val = vals[n]
                     if val: val = val.strip()
                     req.args[ cols[n] ] = val
-            return self._channelsconf_append(req)
+            return self._channelsconf_append(req, session)
         else:
-            return "NO."
+            return "NO('%s')" % line.strip()
     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(req)
         for entry in entries:
-            vals = []
-            for col in cols:
-                vals += [ ustr(getattr(entry, col))]
-            yield (":".join(vals))
+            val = "%s:%s:%s:%s:%s:%s:%s:%s" % (
+                    entry.title or "",
+                    entry.frequency or "",
+                    entry.polarity or "",
+                    entry.source or "",
+                    entry.symbolrate or "",
+                    entry.vpid or "",
+                    entry.apid or "",
+                    entry.tpid  or "")
+            yield val

File src/dvbcronrecording/computer.py

 import time
 import re
 import datetime
-
 import logging
 
-DEBUG = True
+from trac.core import Component #, implements
+from trac.config import Option, ListOption, FloatOption, BoolOption
 
-logg = logging.getLogger("DvbCronRecordingComputer")
-if DEBUG:
-    logg.addHandler(logging.FileHandler("/tmp/DvbCronRecordingComputer.log"))
-    logg.setLevel(logging.DEBUG)
+try:
+    from channels import DvbCronRecordingChannelsPlugin
+except:
+    DvbCronRecordingChannelsPlugin = None
 
-onlydate_rank = 0.5 # +1
-rankup_channels = [ "3sat", "arte" ]
-rankup_plus = 0.3
+DEBUG = False
+PACKAGE = 'dvbcronrecording'
+NAV = 'recordings'
+URL = 'recording'
+
+default_adapter = u"0"
 
 def intnull(value, default = None):
     if value is None: return default
         self.message = ""
         self.rank = 0.0
         self.cloned = None
-        self.tuning_channel = None
-        self.tuning_adapter = None
+        self.adapter = None
     def clone(self):
         item = RecorderItem()
         item.entry = self.entry
         item.message = self.message
         item.rank = self.rank 
         item.cloned = self
-        item.tuning_channel = self.tuning_channel
-        item.tuning_adapter = self.tuning_adapter
+        item.adapter = self.adapter
         return item
     def __unicode__(self):
         return u"(%s) '%s'" % (unicode(self.months), unicode(self.title))
 class RecorderGroup:
     def __init__(self):
         self.items = [] # RecordingItem
+        self.weekday = None
         self.newtimeMMM = None
         self.endtimeMMM = None
         self.extratimeM = None
+        self.adapter = None
     def __unicode__(self):
         return u"["+unicode(",".join([unicode(item) for item in self.items]))+"]"
 
             yield group
     def addgroup(self, group):
         self._groups += [ group ]
+    def sort(self):
+        """ weekday, starttime, adapter """
+        groups = sorted(self._groups, key = lambda x: (x.weekday, x.newtimeMMM, x.adapter))
+        self._groups = groups
+    def timelist_sort(self):
+        """ datetimeX, datetimeY, adapter from timelist_groups() """
+        groups = sorted(self._groups, key = lambda x: (x.datetimeX, x.datetimeY, x.adapter))
+        self._groups = groups
 
 class RecorderFlatPlan:
     def __init__(self):
         self.newtimeMMM = 0
         self.endtimeMMM = 0
         self.extratimeM = 0
+        self.adapter = None
+        self.adapters = [ default_adapter ]
         self.set(orig)
     def set(self, orig):
         if orig is not None:
 
 months_ahead = 3
     
-class TracSimpleRecorderComputer:
+class DvbCronRecordingPlanComputer(Component):
+    """ it is a component but it does not have its own database tables - it 
+        just helps to turn the recording list into a proper recording plan. """
+        
+    algorithm = Option(PACKAGE, "planning-algorithm", "plan4",
+                       doc="Choose the planning type plan1,plan2,plan3,plan4")
+    rankup_channels = ListOption(PACKAGE, "planning-rankup-channels","3sat,arte",
+                       doc="Choose the channels to implicitly rank higher")
+    rankup_value = FloatOption(PACKAGE, "planning-rankup-value", 0.3,
+                        doc="Amount of rank increment for the rankup-channels") 
+    onlydate_rankup = FloatOption(PACKAGE, "planning-onlydate-rankup", 0.5,
+                        doc="Amount of rank increment for single date recordings") 
+    
+    def __init__(self):
+        self._adapters = {}
+        self._adapterlist = None
+        self.logg = logging.getLogger(__name__)
+    
+    def adapters(self, channelname):
+        if channelname in self._adapters:
+            return self._adapters[channelname]
+        return [ default_adapter ]
+    def set_adapters_from_channels_list(self, channelslist):
+        for entry in channelslist:
+            channelname = entry.channelname
+            adapter = entry.adapter
+            if channelname not in self._adapters:
+                self._adapters[channelname] = []
+            if adapter not in self._adapters[channelname]:
+                self._adapters[channelname] += [ adapter ]
+        self.update_adapterlist()
+    def update_adapterlist(self):
+        """ for all channels create the union of their available adapters """
+        adapterlist = []
+        for adapters in self._adapters.values():
+            for adapter in adapters:
+                if adapter and adapter not in adapterlist:
+                    adapterlist += [ adapter ]
+        self._adapterlist = adapterlist
+    def adapterlist(self):
+        """ get the union of available adapters used in configured channels """
+        if self._adapterlist is None:
+            self.updated_adapterlist()
+        adapterlist = self._adapterlist[:]
+        if not adapterlist:
+            return [ default_adapter ]
+        if default_adapter not in adapterlist:
+            adapterlist += [ default_adapter ]
+        return adapterlist
+            
+    def channels_list(self):
+        if DvbCronRecordingChannelsPlugin is None:
+            return [ 0 ]
+        channels = DvbCronRecordingChannelsPlugin(self.env)
+        return channels.channels_list()
+    
+    # ======================================================================
     def zero(self, spec):
         if not spec: return 0
         return int(spec)
     def recordinglist_to_recorderitems(self, entries, ahead = months_ahead):
         allowed_months = self.get_allowed_months(ahead)
         past_days = self.get_past_days_this_month()
-        logg.info("allowed_months %s" % (str(allowed_months)))    
+        self.logg.debug("allowed_months %s" % (str(allowed_months)))    
         for entry in entries:
             if entry.status == "no": 
-                logg.debug("status disabled for %s", entry.title) 
+                self.logg.debug("status disabled for %s", entry.title) 
                 continue
             item = RecorderItem()
             item.channelname = entry.channelname
             item.title = entry.title
             if not item.title:
-                logg.debug("item had not title") 
+                self.logg.debug("item had not title") 
                 continue            
             if not item.channelname: 
-                logg.debug("no channel name for '%s", item.title) 
+                self.logg.debug("no channel name for '%s", item.title) 
                 continue            
             m = re.match("(\d+)[.](\d+)[.]", entry.onlydate)
             if m:
-                logg.debug("onlydate %s for %s", entry.onlydate, item.title)
+                self.logg.debug("onlydate %s for %s", entry.onlydate, item.title)
                 try:
                     onlyday = intnull(m.group(1))
                     onlymonth = intnull(m.group(2))
                     if not onlyday: continue
                     if not onlymonth: continue
                     if onlymonth not in allowed_months:
-                        logg.debug("onlymonth not in allowed_months '%s'", item.title) 
+                        self.logg.debug("onlymonth not in allowed_months '%s'", item.title) 
                         continue
                     if (onlyday, onlymonth) in past_days: 
-                        logg.debug("(onlyday, onlymonth) in past_days '%s'", item.title) 
+                        self.logg.debug("(onlyday, onlymonth) in past_days '%s'", item.title) 
                         continue
                     item.onlyday = onlyday
                     item.months = [ onlymonth ]
-                    logg.info("onlyday %s onlymonths %s for %s", item.onlyday, item.months, item.title)
+                    self.logg.debug("onlyday %s onlymonths %s for %s", item.onlyday, item.months, item.title)
                 except Exception:
-                    logg.debug("conversion error %s '%s'", entry.onlydate, item.title)
+                    self.logg.debug("conversion error %s '%s'", entry.onlydate, item.title)
                 item.weekday = intnull(entry.weekday)
             else: 
                 item.weekday = intnull(entry.weekday)
                 if item.weekday is None:
-                    logg.debug("item.weekday is None '%s'", item.title) 
+                    self.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.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))
+            if item.channelname in self.rankup_channels:
+                item.rank += self.rankup_value
+            self.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))
             for month in item.months: 
                 if month not in onlymonths:
                     onlymonths.append(month)
-        logg.info("onlymonths %s", onlymonths)
+        self.logg.debug("onlymonths %s", onlymonths)
         regularmonths = [ month for month in xrange(1,13) if month not in onlymonths ]
-        logg.info("regularmonths %s", regularmonths)
+        self.logg.debug("regularmonths %s", regularmonths)
         localtime = time.localtime()
         this_month = localtime.tm_mon
         this_year = localtime.tm_year
                     # no generate the recordings for that day
                     items = self.clone_filtered_for_date(recorderitems, date)
                     for group in self.recordergroups_from_filtered(items):
-                        logg.info("onlydate group %s", unicode(group))
+                        self.logg.debug("onlydate group %s", unicode(group))
                         yield group
                 except ValueError:
                     pass # expected
         items = self.clone_filtered_for_regular(recorderitems, regularmonths)
-        logg.info("=======================================================")
+        self.logg.debug("=======================================================")
         items = list(items)
         for item in items:
-            logg.info("regular item %s", unicode(item))
+            self.logg.debug("regular item %s", unicode(item))
         for group in self.recordergroups_from_filtered(items):
-            logg.info("regular group %s", unicode(group))
+            self.logg.debug("regular group %s", unicode(group))
             yield group
     def clone_filtered_for_date(self, recorderitems, date):
         for item in recorderitems:
             newitem.months = [ date.month ]
             newitem.weekday = date.weekday()
             if item.onlyday: 
-                newitem.rank += onlydate_rank
+                newitem.rank += self.onlydate_rankup
             yield newitem
     def clone_filtered_for_regular(self, recorderitems, regularmonths):
         for item in recorderitems:
             extratimeM = item.extratimeM 
             if group is None: # first run
                 group = RecorderGroup()
+                group.weekday = item.weekday
                 group.newtimeMMM = newtimeMMM
             elif group.endtimeMMM + group.extratimeM < newtimeMMM + nextday:
                 yield group # non-recording interval
                 group = RecorderGroup()
+                group.weekday = item.weekday
                 group.newtimeMMM = newtimeMMM
             else: # overlapping?
                 assert previous is not None # because of first run
                 if item.weekday != previous_ended_weekday:
                     yield group # non-recording interval
                     group = RecorderGroup()
+                    group.weekday = item.weekday
                     group.newtimeMMM = newtimeMMM
                 pass 
             if endtimeMMM < newtimeMMM:
             of newtime-endtime is the recording time interval. """
         yield self.adjust_recordergroup(group) 
     def adjust_recordergroup(self, group):
-        logg.info("adjust group %s", unicode(group))
+        self.logg.debug("adjust group %s", unicode(group))
         nextday = 0
         previous = None
         for item in group.items:
             nextday = 0
         return group
     def make_cronmonths(self, group):
-        logg.info("adjust group %s", unicode(group))
+        self.logg.debug("adjust group %s", unicode(group))
         for item in group.items:
             ranges = []
             months = item.months
         for item in itemlist: 
             if not group:
                 group = RecorderGroup()
+                group.weekday = item.weekday
                 group.firstdate = item.onlydate
                 group.newtimeMMM = item.newtimeMMM
                 group.endtimeMMM = item.endtimeMMM
             if group.endtimeMMM < item.newtimeMMM + deltaMMM:
                 yield group
                 group = RecorderGroup()
+                group.weekday = item.weekday
                 group.firstdate = item.onlydate
                 group.newtimeMMM = item.newtimeMMM
                 group.endtimeMMM = item.endtimeMMM
                             if date.month not in item.months: continue
                         else:
                             if item.weekday != date.weekday(): continue
-                        logg.info("* %s => [%s.%s.] w%s '%s'", date, item.onlyday, item.months, item.weekday, item.title)
+                        self.logg.debug("* %s => [%s.%s.] w%s '%s'", date, item.onlyday, item.months, item.weekday, item.title)
                         newtimeMMM = item.newtimeMMM
                         endtimeMMM = item.endtimeMMM
                         if endtimeMMM < newtimeMMM: endtimeMMM += 24 * 60
                         elem = RecorderTime(item)
                         elem.datetimeX = date + datetime.timedelta(minutes = newtimeMMM)
                         elem.datetimeY = date + datetime.timedelta(minutes = endtimeMMM)
+                        elem.adapters = self.adapters(item.channelname)
                         # elem.message += " (([%s .. %s] from %s..%s))" % (elem.datetimeX, elem.datetimeY, newtimeMMM, endtimeMMM)
                         yield elem
                 except:
                     pass
-    def adjust_recordertimelist(self, recordertimelist):
+    def adjust_recordertimelist(self, recordertimelist, adapter = None):
         """ recordertimelist contains absolute X Y dates """
         minimum = datetime.timedelta(minutes = 2)
         previous = None
         for current in recordertimelist:
             if current.deleted:
                 continue
+            if adapter is not None and current.adapter is not None:
+                if adapter != current.adapter:
+                    continue
             if previous is None:
                 previous = current
                 continue
                 if overlapM <= previous.extratimeM:
                     previous.extratimeM -= overlapM
                     previous.datetimeY -= overlapY
-                    previous.message += " cut extratimeM %03d," % overlapM
+                    if current.item.channelname != previous.item.channelname:
+                        previous.message += " cut extratimeM %03d," % overlapM
+                    else:
+                        previous.message += " continues"
                     previous = current
                 else: 
                     extraY = datetime.timedelta(minutes = previous.extratimeM)
                         if previous.datetimeY > current.datetimeX:
                             previous.datetimeY = current.datetimeX
                             previous.endtimeMMM = current.newtimeMMM
-                            previous.message += " end time set to next start %s," % MMMtoHHMM(current.newtimeMMM)
+                            if current.item.channelname != previous.item.channelname:
+                                previous.message += " end time set to next start %s," % MMMtoHHMM(current.newtimeMMM)
+                            else:
+                                previous.message += " continue"
                         if previous.datetimeY - previous.datetimeX < minimum:
                             previous.deleted = True
                             previous.datetimeY = previous.datetimeX
                         #    current.item.onlyday = current.datetimeX.day
                         #    current.item.months = [ current.datetimeX.month ] 
                         current.newtimeMMM = previous.endtimeMMM + previous.extratimeM
-                        current.message += " set start time to previous end %s," % MMMtoHHMM(current.newtimeMMM)
+                        if current.item.channelname != previous.item.channelname:
+                            current.message += " set start time to previous end %s," % MMMtoHHMM(current.newtimeMMM)
+                        else:
+                            current.message += " continue"
                     if current.datetimeY - current.datetimeX < minimum:
                         current.deleted = True
                         current.datetimeX = current.datetimeY
                         current.message += " and deleted as it is too short now"
                     else:
                         previous = current
-    def timelist_groups(self, timelist):
+    def assign_adapters_for_timelist(self, recordertimelist):
+        """ initialized recording adapter - if recordings overlap
+            and one of them has multiple adapter options than assign
+            one of the secondary adapters available.  """
+        previous = None
+        for current in recordertimelist:
+            if current.deleted:
+                continue
+            if current.adapters:
+                current.adapter = current.adapters[0]
+            if previous is None:
+                previous = current
+                continue
+            if previous.datetimeY <= current.datetimeX:
+                previous = current
+                continue # nothing to do - there is a gap
+            if previous.datetimeX > current.datetimeX:
+                raise Exception("previous starts after current")
+            overlapY = previous.datetimeY - current.datetimeX
+            overlapM = deltaM(overlapY)
+            assert overlapM > 0
+            if previous.item.channelname == current.item.channelname:
+                previous = current
+                continue
+            done = False
+            if not done:
+                if previous.item.rank < current.item.rank:
+                    for adapter in previous.adapters:
+                        if adapter != current.adapter:
+                            previous.adapter = adapter
+                            done = True; break
+                    for adapter in current.adapters:
+                        if adapter != previous.adapter:
+                            current.adapter = adapter
+                            done = True; break
+                else:
+                    for adapter in current.adapters:
+                        if adapter != previous.adapter:
+                            current.adapter = adapter
+                            done = True; break
+                    for adapter in previous.adapters:
+                        if adapter != current.adapter:
+                            previous.adapter = adapter
+                            done = True; break
+            if not done:
+                # can not split overlapping recordings to different adapers
+                pass
+    def timelist_groups(self, timelist, adapter = None):
         group = None
         previous = None
         previousgroup = None
         predeletes = []
         seen = ""
         for elem in timelist:
+            self.logg.debug("timelist_groups: adapter '%s' -vs- '%s'",
+                      adapter, elem.adapter)            
+            if adapter is not None and elem.adapter is not None:
+                if adapter != elem.adapter:
+                    continue
             item = elem.item.clone()
             item.newtimeMMM = elem.newtimeMMM
             item.endtimeMMM = elem.endtimeMMM
             item.weekday = elem.datetimeX.weekday()
             item.onlyday = elem.datetimeX.day
             item.months = [ elem.datetimeX.month ]
+            item.adapter = elem.adapter
             if item.newtimeMMM != elem.item.newtimeMMM:
                 item.message += " [%s]>>[%s]" % (MMMtoHHMM(elem.item.newtimeMMM), MMMtoHHMM(item.newtimeMMM))
             if item.endtimeMMM != elem.item.endtimeMMM:
                 else:
                     previous = elem
                     group = RecorderGroup()
+                    group.datetimeX = elem.datetimeX
+                    group.datetimeY = elem.datetimeY
+                    group.weekday = item.weekday
                     group.newtimeMMM = item.newtimeMMM
                     group.endtimeMMM = item.endtimeMMM
                     group.extratimeM = item.extratimeM
+                    group.adapter = item.adapter
                     group.items = predeletes + [ item ]
                     predeletes = []
             else:
                     group.items += [ item ]
                 else:
                     previous = elem
+                    group.datetimeY = elem.datetimeY
                     group.endtimeMMM = item.endtimeMMM
                     group.extratimeM = item.extratimeM
                     group.items += [ item ]
             group.deleted = False
             group.weekday = group.items[0].weekday
             group.hasonlydays = 0
+            key = ""
             for item in group.items:
+                key += "%02i%04i%04i(%s)" % (item.weekday, item.newtimeMMM, item.endtimeMMM, item.channelname)
                 if item.cloned and item.cloned.onlyday:
                     group.hasonlydays += 1
-            key = (group.weekday, group.newtimeMMM, group.endtimeMMM, group.extratimeM)
+            # key = (group.weekday, group.newtimeMMM, group.endtimeMMM, group.extratimeM)
             if key not in blocks:
                 blocks[key] = BlockGroup()
             blocks[key].groups += [ group ]
             if group.deleted: continue
             newplan.addgroup(group)
         return newplan           
+    def new_reduce_regular_groups(self, plan):
+        class Similar:
+            def __init__(self, value):
+                self.serial = value
+                self.groups = []
+        serial = 1000
+        similar = {}
+        for group in plan.groups():
+            group.deleted = False
+            group.weekday = group.items[0].weekday
+            group.hasonlydays = []
+            key = ""
+            for item in group.items:
+                if not key and item.adapter:
+                    key = "(%s)" % item.adapter
+                key += "%02i%04i%04i(%s)" % (item.weekday, item.newtimeMMM, item.endtimeMMM, item.channelname)
+                if item.cloned and item.cloned.onlyday:
+                    group.hasonlydays += [ item.cloned.months[0] ]
+            if key not in similar:
+                similar[key] = Similar(serial)
+                serial += 1
+            similar[key].groups += [ group ]
+        hasonlydays = []
+        for key, sim  in similar.items():
+            sim.hasonlydays = []
+            for group in sim.groups:
+                for month in group.hasonlydays:
+                    if month not in sim.hasonlydays:
+                        sim.hasonlydays += [ month ]
+                    if month not in hasonlydays:
+                        hasonlydays += [ month ]
+        for key, sim  in similar.items():
+            recurring = None
+            for group in sim.groups:
+                group.deleted = False
+                group.recurring = False
+                keep = False
+                if group.hasonlydays:
+                    keep = True
+                for item in group.items:
+                    for month in item.months:
+                        if month in hasonlydays:
+                            keep = True
+                if not keep:
+                    if not recurring:
+                        recurring = group
+                        group.recurring = True
+                    else:
+                        group.deleted = True
+            for group in sim.groups:
+                months = [ month for month in xrange(1,13) if month not in hasonlydays ]
+                for item in group.items:
+                    if group.recurring:
+                        item.onlyday = None
+                        item.months = months
+                self.make_cronmonths(group)
+        newplan = RecorderGroupPlan()
+        for group in plan.groups():
+            if group.deleted: continue
+            newplan.addgroup(group)
+        return newplan
     def plan3(self, entries):
         """ main entry point """
+        self.logg.info("plan3")
         timelist = list(self.recordinglist_to_recordertimelist(entries))
         old_deleted = 0
         for round in xrange(2):
         for group in self.timelist_groups(timelist):
             plan.addgroup(group)
         if True:
+            return self.new_reduce_regular_groups(plan)
+        if True:
             return self.reduce_regular_groups(plan)
         return plan
+    def plan4(self, entries):
+        """ main entry point """
+        self.logg.info("plan4")
+        timelist = list(self.recordinglist_to_recordertimelist(entries))
+        self.assign_adapters_for_timelist(timelist)
+        old_deleted = 0
+        for round in xrange(2):
+            timelist = sorted(timelist, key = lambda x: x.datetimeX )
+            for adapter in self.adapterlist():
+                self.adjust_recordertimelist(timelist, adapter)
+            deleted = [ elem for elem in timelist if elem.deleted ]
+            new_deleted = len(deleted)
+            if old_deleted < new_deleted:
+                old_deleted = new_deleted
+                continue
+        timelist = sorted(timelist, key = lambda x: x.datetimeX )
+        plan = RecorderGroupPlan()
+        for adapter in self.adapterlist():
+            for group in self.timelist_groups(timelist, adapter):
+                plan.addgroup(group)
+        self.logg.info("adapterlist = %s", self.adapterlist())
+        if True:
+            plan.timelist_sort()
+            return self.new_reduce_regular_groups(plan)
+        else:
+            plan.timelist_sort()
+            return plan
             
     def plan(self, entries):
-        return self.plan3(entries)
+        if self.algorithm == "plan1":
+            return self.plan1(entries)
+        if self.algorithm == "plan2":
+            return self.plan2(entries)
+        self.set_adapters_from_channels_list(self.channels_list())
+        if self.algorithm == "plan3":
+            return self.plan3(entries)
+        if self.algorithm == "plan4":
+            return self.plan4(entries)
+        #else:
+        return self.plan4(entries)
 
 def deltaM(td):
     return td.seconds / 60 + (td.days * 24 * 60)

File src/dvbcronrecording/core.de.po

-msgid ""
-msgstr ""
-"Content-Type: text/plain; charset=UTF-8\n"
-"Report-Msgid-Bugs-To: guidod@gmx.de\n"
-"Plural-Forms: nplurals=3; plural=(n==0 ? 0 : (n == 1 ? 1 : 2));\n"
-
-msgid ".recorder."
-msgstr "Recorder"
-
-msgid ".recording."
-msgstr "Recorder"
-
-msgid "insert" 
-msgstr "einfügen"
-
-msgid "delete" 
-msgstr "[löschen]"
-
-msgid "update"
-msgstr "aktualisieren"
-
-
-msgid "play"
-msgstr "Abspielen"
-
-msgid "mplay"
-msgstr "mit MPlayer"
-
-msgid "starts"
-msgstr "Startzeit"
-
-msgid "ends"
-msgstr "Endzeit"
-
-msgid "extra"
-msgstr "Extra"
-
-msgid "weekday"
-msgstr "Wo-Tag"
-
-msgid "onlydate"
-msgstr " +Datum"
-
-msgid "status"
-msgstr "Status"
-
-msgid "priority"
-msgstr "Priorität"
-
-msgid "prio"
-msgstr "Prio"
-
-msgid "adapter"
-msgstr "Adapter"
-
-msgid "channel.conf title"
-msgstr "Bezeichnung in der channel.conf"
-
-msgid "channel.conf show"
-msgstr "channel.conf Ansicht"
-
-msgid "scansettings"
-msgstr "Scan-Einstellungen"
-
-msgid "satellite"
-msgstr "Satellit"
-
-msgid "transponder"
-msgstr "Transponder"
-
-msgid "action buttons"
-msgstr "Betriebsschalter"
-
-msgid "newtime"
-msgstr "Startzeit"
-
-msgid "endtime"
-msgstr "Endzeit"
-
-msgid "hint"
-msgstr "Hinweis"
-
-msgid "NEW"
-msgstr "NEU"
-
-msgid "SAVE"
-msgstr "SPEICHERN"
-
-msgid "Show Video"
-msgstr "Video Anzeigen"
-
-msgid "Recorded Files Overview"
-msgstr "Überblick aufgezeichneter Dateien"
-
-msgid "Makefile Overview"
-msgstr "Make-Datei Überblick"
-
-msgid "Cronfile Overview"
-msgstr "Cron-Datei Überblick"
-
-msgid "Recording Plan"
-msgstr "Der Aufnahmeplan"
-
-msgid "Cron Activate"
-msgstr "Cron Aktivierung"
-
-msgid "Recordings List"
-msgstr "Die Recorder-Liste"
-
-
-# ---------------------------------------------------------
-
-msgid "fully internationalized"
-msgstr "Umlaute sind jetzt möglich."
-
-msgid "-.prio"
-msgstr "--"
-
-msgid "0.prio"
-msgstr "hmm"
-
-msgid "1.prio" 
-msgstr "egal"
-
-msgid "2.prio"
-msgstr "kann"
-
-msgid "3.prio"
-msgstr "soll"
-
-msgid "4.prio"
-msgstr "muss"
-
-msgid "5.prio"
-msgstr "muss!"
-
-msgid "6.prio"
-msgstr "top!"
-
-msgid "7.prio"
-msgstr "über!"
-
-msgid "-.status"
-msgstr "--"
-
-msgid "no.status"
-msgstr "no"
-
-msgid "ok.status"
-msgstr "ok"
-
-msgid "prime-time-channel"
-msgstr "pro7"
-
-msgid "prime-time-starts"
-msgstr "20:15"
-
-msgid "prime-time-ends"
-msgstr "22:10"
-
-msgid "prime-time-extra"
-msgstr "7"
-
-msgid "prime-time-movie"
-msgstr "der-hauptfilm-heute"
-
-msgid "cron.will.be.updated.every"
-msgstr "Der Zeitdienst übernimmt die Änderungen jeweils"
-
-msgid "ERROR:"
-msgstr "FEHLER:"
-
-msgid "create.directory '%(directory)s':"
-msgstr "Anlegen des Verzeichnisses '%(directory)s':"
-
-msgid "save.to.file '%(filename)s':"
-msgstr "Speichern in Datei '%(filename)s':"
-
-msgid "done.save.to.file '%(filename)s'"
-msgstr "'%(filename)s' Datei wurde gespeichert"
-
-msgid "(%(n)s line)"
-msgid_plural "(%(n)s lines)"
-msgstr[0] "(leer)"
-msgstr[1] "(eine Zeile)"
-msgstr[2] "(%(n)s Zeilen)"

File src/dvbcronrecording/core.py

 from trac.config import Option, ListOption, PathOption, BoolOption
 
 from translate import Translate #@UnresolvedImport
-from computer import TracSimpleRecorderComputer, MMMtoHHMM
+from computer import DvbCronRecordingPlanComputer, MMMtoHHMM
 try:
     from channels import DvbCronRecordingChannelsPlugin
 except:
 
 CHANNELSCONF = "~/.szap/{adapter}/channels.conf"
 
-from db.session import db_cnx, commit #@UnresolvedImport
-from db.schema import RecordingList
+default_adapter = u"0"
+
+from dvbcronrecording.db.session import db_cnx, commit
+from dvbcronrecording.db.schema import RecordingList #@UnresolvedImport
 
 def _(text): return text
 
 
 class MakefileEntry: pass
 
+class AdapterList:
+    def __init__(self):
+        self._adapters = []
+    def number(self, adapter):
+        n = len(self._adapters)
+        if adapter not in self._adapters:
+            self._adapters += [ adapter ]
+        else:
+            n = self._adapters.index(adapter)
+        return n
+    def name(self, adapter):
+        return chr(ord('A') + self.number(adapter))
+    def adaptername(self, item):
+        return self.name(getattr(item, "adapter", default_adapter))
+
 """
   here we are.
 """
     def get_navigation_items(self, req):
         if not req.perm.has_permission(LIST_VIEW):
             return
-        translate = Translate("core", req.locale)
+        translate = Translate(PACKAGE, req.locale)
         yield ('mainnav', NAV, Markup('<a href="%s">%s</a>' % 
           (req.href(URL), translate.get(self.title, TITLE))))
 
     """
     def process_request(self, req):
         req.perm.assert_permission(LIST_VIEW)
-        translate = Translate("core", req.locale, req.languages )
+        translate = Translate(PACKAGE, req.locale, req.languages )
 
         page = req.args['page']
         if not page: page = "list"
             data['entries'] = self._makefile_entries(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
+            add_stylesheet(req, PACKAGE+'/css/dvbcronrecording.css')
             add_script(req, 'common/js/trac.js')
             return ('recordingmakefile.html', data, None)
         if page in [ 'cronfile' ]:
             data['cronlines'] = self._crontablines(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
+            add_stylesheet(req, PACKAGE+'/css/dvbcronrecording.css')
             add_script(req, 'common/js/trac.js')
             return ('recordingcronfile.html', data, None)
         if page in [ 'plan' ]:
             data['recordings'] = self._recording_plan(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
+            add_stylesheet(req, PACKAGE+'/css/dvbcronrecording.css')
             add_script(req, 'common/js/trac.js')
             return ('recordingplan.html', data, None)
         if page in [ 'activate' ]:
             data['cronlines'] = crontab_list # + [ " ============= "] + makefile_list
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
+            add_stylesheet(req, PACKAGE+'/css/dvbcronrecording.css')
             add_script(req, 'common/js/trac.js')
             return ('recordingcronfile.html', data, None)
         if page in [ "save" ]:
             data['text'] = self.loadtext(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
+            add_stylesheet(req, PACKAGE+'/css/dvbcronrecording.css')
             add_script(req, 'common/js/trac.js')
             return ('recordingeditor.html', data, None)
         if page in [ "edit" ]:
             data['text'] = self.loadtext(req)
             data['_'] = translate
             add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
+            add_stylesheet(req, PACKAGE+'/css/dvbcronrecording.css')
             add_script(req, 'common/js/trac.js')
             return ('recordingeditor.html', data, None)
             
         
         # database commit and return page content
         commit()
-
-        add_stylesheet(req, 'common/css/wiki.css')
-        add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
-        add_script(req, 'common/js/trac.js')
-        add_script(req, 'common/js/wikitoolbar.js')
-        
+       
         if not message: message = translate("fully internationalized")
         # import locale
         # from trac.util.translation import get_negotiated_locale
         data['channelnamelist'] = [ "" ] + self._channelname_list(req)
         data['_'] = translate
 
+        add_stylesheet(req, 'common/css/wiki.css')
+        add_stylesheet(req, PACKAGE+'/css/recordinglist.css')
+        add_stylesheet(req, PACKAGE+'/css/dvbcronrecording.css')
+        add_script(req, '%s/js/jquery.ui.datepicker.js' % (PACKAGE))
+        if req.locale:
+            add_script(req, '%s/js/jquery.ui.datepicker-%s.js' % (PACKAGE, req.locale))
+        add_script(req, 'common/js/trac.js')
+        add_script(req, 'common/js/wikitoolbar.js')
         return ('recordinglist.html', data, None)
 
     # ====================================================================
         return channels.channelname_list(req)
     
     def _newrecordings_list(self, req):
-        translate = Translate("core", req.locale)
+        translate = Translate(PACKAGE, req.locale)
         channel = translate.get("prime-time-channel", "pro7")
         starts = translate.get("prime-time-start", "20:15")
         ends = translate.get("prime-time-ends", "22:10") 
     # ---------------------------------------------------------------
     
     def savetext(self, req, text):
-        translate = Translate("core", req.locale)
+        translate = Translate(PACKAGE, req.locale)
         msgs = []
         for line in text.split("\n"):
             if line.startswith("+"):
         return "\n".join(list(self.loadtextlines(req)))
     def loadtextlines(self, req):
         entries = self._recordings_list(req)
-        computer = TracSimpleRecorderComputer()
+        computer = DvbCronRecordingPlanComputer(self.env)
         plan = computer.plan(entries)
         for group in plan.groups():
             newtime = MMMtoHHMM(group.newtimeMMM).replace(":","")
         entries = self._recordings_list(req)
         return list(self._recording_plan_from(entries))
     def _recording_plan_from(self, entries):
-        computer = TracSimpleRecorderComputer()
+        computer = DvbCronRecordingPlanComputer(self.env)
         plan = computer.plan(entries)
         for item in plan.items():
             yield item
             return [ 0 ]
         channels = DvbCronRecordingChannelsPlugin(self.env)
         return channels.channelsconf_for_adapter(adapter)
+    def channels_list(self):
+        if DvbCronRecordingChannelsPlugin is None:
+            return [ 0 ]
+        channels = DvbCronRecordingChannelsPlugin(self.env)
+        return channels.channels_list()
 
 
     """ 
     """
     def _makefile_entries(self, req):
         entries = self._recordings_list(req)
-        computer = TracSimpleRecorderComputer()
+        computer = DvbCronRecordingPlanComputer(self.env)
         plan = computer.plan(entries)
+        adapterlist = AdapterList()
         for group in plan.groups():
             if not group.items: continue
             item = group.items[0]
             HH = item.newtimeMMM / 60
             MM = item.newtimeMMM % 60
+            AD = adapterlist.adaptername(item)
             if item.onlyday:
                 DD = str(item.onlyday)
                 EE = ",".join([str(month) for month in item.months ])
                 startcron = "%02d %02d %2s %2s *" % (int(MM), int(HH), DD, EE)
-                startname = "%02d.%02d.%02d.%s" % (int(HH), int(MM), item.onlyday, EE)
+                startname = "%02d.%02d.%02d.%s%s" % (int(HH), int(MM), item.onlyday, EE, AD)
             else:
                 WW = ustr(int(item.weekday)+1)
                 # WEEKDAY: tm_day Mo = 0 / -vs- / crontabs So = 0
                 startcron = "%02d %02d  *  * %s" % (int(MM), int(HH), WW)
-                startname = "%02d.%02d.%s" % (int(HH), int(MM), item.weekday)
+                startname = "%02d.%02d.%s%s" % (int(HH), int(MM), item.weekday, AD)
             lines = []
             for item in group.items:
                 newtimeMMM = item.newtimeMMM
                 runtimeseconds = (endtimeMMM + extratimeM - newtimeMMM) * 60
                 filename = ustr("%s.mpg") % (quote4(item.title))
                 channelname = item.channelname
-                adapter = item.tuning_adapter
-                channel = item.tuning_channel
-                if not adapter: adapter = "0"
-                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                
+                adapter = item.adapter or "0"
+                channel = item.channelname
                 frontend = 0
                 if ":" in adapter:
                     adapter, frontend = adapter.split(":",1)
-                channel = item.channelname
                 channelsconf = self.cron_channelsconf_filename(adapter)
-                format = "%Y%m%d-"+ustr(adapter)+"%a-%H%M"
+                format = "%Y%m%d-%a"+ustr(adapter)+"-%H%M"
                 prefix = self.intodir+"/"
                 cmd = [ u"gnutv -adapter %(adapter)s -frontend %(frontend)s" ]
                 cmd += [ u" -channels %(channelsconf)s" ]
         return int(spec)
         
     def writefile(self, req, filename, lines):
-        translate = Translate("core", req.locale)
+        translate = Translate(PACKAGE, req.locale)
         directory = os.path.dirname(filename)
         try:
             if not os.path.isdir(directory):

File src/dvbcronrecording/db/schema.py

 
-import db4 
+import dvbcronrecording.db.db4 as _db 
 
-metadata = db4.metadata
+metadata = _db.metadata
 tables = metadata.sorted_tables # support old aff() detection
 
-RecordingTuning = db4.RecordingTuning
-RecordingChannels = db4.RecordingChannels
-RecordingChannelsConf = db4.RecordingChannelsConf
-RecordingList = db4.RecordingList
+RecordingTuning = _db.RecordingTuning
+RecordingChannels = _db.RecordingChannels
+RecordingChannelsConf = _db.RecordingChannelsConf
+RecordingList = _db.RecordingList
+
+pass

File src/dvbcronrecording/db/session.py

-from tsab import engine
+from tsab2 import engine, session
 from sqlalchemy.orm import create_session
 
 _session = None
 def context(env):
     global _session
     if _session is None:
-        _session = create_session(bind=engine(env))
+        # _session = session(env) 
+        _session = create_session(bind=engine(env), autocommit=True, autoflush=True)
     return _session
 
 def db_cnx(env): return context(env)

File src/dvbcronrecording/db/tsab2.py

+# -*- coding: utf-8 -*-
+"""
+    Trac SQLAlchemy Bridge
+    ======================
+
+    tracext.sa
+    ~~~~~~~~~~
+
+    This module enables a plugin developer to use SQLAlchemy for database work.
+
+    For some usage example there's nothing better than `source code`__.
+
+    :copyright: 2008 by Armin Ronacher, Pedro Algarvio.
+    :license: WTFPL.
+
+
+    .. __: http://tl10nm.ufsoft.org/browser
+
+"""
+#            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+#                    Version 2, December 2004
+#
+# Copyright (C) 2008 Armin Ronacher, Pedro Algarvio.
+#  14 rue de Plaisance, 75014 Paris, France
+# Everyone is permitted to copy and distribute verbatim or modified
+# copies of this license document, and changing it is allowed as long
+# as the name is changed.
+#
+#            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+#   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+#
+#  0. You just DO WHAT THE FUCK YOU WANT TO.
+#
+# http://en.wikipedia.org/wiki/WTFPL
+#
+# Authors:
+#    Armin Ronacher <armin.ronacher@active-4.com>
+#    Pedro Algarvio <ufs@ufsoft.org>
+#    Guido Draheim <guidod@gmx.de>
+
+#__version__     = '0.2.0'
+#__author__      = 'Armin Ronacher, Pedro Algarvio, Guido Draheim'
+#__email__       = 'armin.ronacher@active-4.com, ufs@ufsoft.org, guidod@gmx.de'
+#__package__     = 'TracSQLAlchemyBridge'
+#__license__     = 'WTFPL'
+#__url__         = 'http://trac-hacks.org/wiki/TracSqlAlchemyBridgeIntegration'
+#__summary__     = 'Bridge for a plugin developer to use SQLAlchemy with trac'
+#__description__ = __doc__
+
+from weakref import WeakKeyDictionary
+from threading import local #@UnusedImport
+from sqlalchemy.engine import create_engine
+from sqlalchemy.orm import create_session, scoped_session #@UnusedImport
+from sqlalchemy.pool import NullPool
+from sqlalchemy.engine.url import URL
+from trac.db.api import DatabaseManager
+from trac.db.util import ConnectionWrapper
+
+
+class TracPool(NullPool):
+    def __init__(self, creator, pool_size=5, **kw):
+        NullPool.__init__(self, pool_size, **kw)
+        self.env = None
+        self.connection = None
+        self._creator = self._do_creator # override!!
+    def use(self, env):
+        self.env = env
+        self.logger = env.log
+        return self
+    def status(self):
+        return "TracPool"
+    def _do_creator(self):
+        if self.connection is None:
+            self.connection = self.env.get_db_cnx()
+        cnx = self.connection.cnx
+        while isinstance(cnx, ConnectionWrapper):
+            cnx = cnx.cnx
+        return cnx
+    def _do_return_conn(self, conn):
+        if self.connection:
+            # Don't let SQLAlchemy close the connection, trac handles that
+            ###  self.connection.close()
+            # just allow the garbage collector to see that it is unused
+            self.connection = None
+
+_engines = WeakKeyDictionary()
+
+def engine(env):
+    engine = _engines.get(env)
+    if engine is None:
+        schema = DatabaseManager(env).connection_uri.split(':')[0]
+        echo = env.config.get('logging', 'log_level').lower() == 'debug'
+        
+        class TracEnvPool(TracPool):
+            def __init__(self, creator, pool_size=5, **kw):
+                TracPool.__init__(self, creator, pool_size, **kw)
+                self.use(env)
+
+        engine = create_engine(URL(schema), poolclass=TracEnvPool,
+                               creator=None, echo=echo)
+        if echo:
+            # make sqlalchemy log to trac's logger
+            if hasattr(env, 'get_logger'):
+                engine.logger = env.get_logger(__name__)
+            else:
+                engine.logger = env.log
+        _engines[env] = engine
+    try:
+        return engine.begin()
+    except AttributeError:
+        # Older SQLAlchemy
+        return engine
+
+
+def session(env):
+    try:
+        db_session = create_session(engine(env), autocommit=True)
+        db_session.begin()
+    except TypeError:
+        # Older SqlAlchemy
+        db_session = create_session(engine(env), transactional=True)
+        db_session.add = db_session.save
+    # Keep session opened for as long as possible by keeping it attached to
+    # env; avoids it to be garbage collected since trac explicitly calls gc
+    # to avoid memory leaks
+    env.db_session = db_session
+    return db_session
+
+__all__ = ['engine', 'session']

File src/dvbcronrecording/dvbcronrecording.de.po

+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Report-Msgid-Bugs-To: guidod@gmx.de\n"
+"Plural-Forms: nplurals=3; plural=(n==0 ? 0 : (n == 1 ? 1 : 2));\n"
+
+msgid "channel list"
+msgstr "Sender-Liste"
+
+msgid "Channel List"
+msgstr "Die Sender-Liste"
+
+msgid "channel"
+msgstr "Sender"
+
+msgid "channelname"
+msgstr "Senderkennung"
+
+msgid "recorder list"
+msgstr "Recorder-Liste"
+
+msgid "recording list"
+msgstr "Recorder-Liste"
+
+msgid "activate!"
+msgstr "Aktivieren!"
+
+msgid "makefile"
+msgstr "Make-Datei"
+
+msgid "cronfile"
+msgstr "Cron-Datei"
+
+msgid "edit as text"
+msgstr "(Texteditor)"
+
+msgid "show recorded files"
+msgstr "Aufgezeichnete Dateien"
+
+msgid "title"
+msgstr "Bezeichnung"
+
+msgid "recording plan"
+msgstr "Aufnahmeplan"
+
+msgid "tuning list"
+msgstr "Tuning-Liste"
+
+msgid "Tuning List"
+msgstr "Die Tuning-Liste"
+
+msgid "time"
+msgstr "Zeit"
+
+msgid "size"
+msgstr "Größe"
+
+msgid "name"
+msgstr "Name"
+
+msgid "Pages:"
+msgstr "Seiten:"
+
+msgid "shown"
+msgstr "zeigt"
+
+msgid " of "
+msgstr " von "
+
+msgid "entries"
+msgstr "Einträgen"
+
+msgid "%(n)s entry"
+msgid_plural "%(n)s entries"
+msgstr[0] "keine Einträge"
+msgstr[1] "%(n)s Eintrag"
+msgstr[2] "%(n)s Einträge"
+
+msgid "channel type"
+msgstr "Kanaltyp"
+
+msgid "video channels"
+msgstr "Fernsehkanäle"
+
+msgid "radio channels"
+msgstr "Radiokanäle"
+
+msgid "data channels"
+msgstr "Datenkanäle"
+
+msgid "all channel types"
+msgstr "Alle Sendekanäle"
+
+msgid "channel subselection"
+msgstr "Teilauswahl Kanäle"
+ 
+msgid "channels.conf editor"
+msgstr "channels.conf Editor"
+
+#  channels.de.po
+
+msgid "insert"
+msgstr "einfügen"
+
+msgid "delete"
+msgstr "[loeschen]"
+
+msgid "update"
+msgstr "aktualisieren"
+
+msgid "play"
+msgstr "Abspielen"
+
+msgid "mplay"
+msgstr "mit MPlayer"
+
+
+msgid "adapter"
+msgstr "Adapter"
+
+msgid "channel.conf title"
+msgstr "Bezeichnung in der channel.conf"
+
+msgid "channel.conf show"
+msgstr "channel.conf Ansicht"
+
+msgid "channel.conf text"
+msgstr "channel.conf Text"
+
+msgid "action buttons"
+msgstr "Arbeitsgang"
+
+msgid "status"
+msgstr "Status"
+
+msgid "NEW"
+msgstr "NEU"
+
+msgid "SAVE"
+msgstr "SPEICHERN"
+