# # # = Scheduler # # == Base class # # Author:: Robert Sharp # Copyright:: Copyright (c) 2011 Robert Sharp # License:: Open Software Licence v3.0 # # This software is licensed for use under the Open Software Licence v. 3.0 # The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php # and in the file copyright.txt. Under the terms of this licence, all derivative works # must themselves be licensed under the Open Software Licence v. 3.0 # # A base class for the Jeeves Tuner and Scheduler # # This class needs to be inherited to create specific subclasses for different tuners # require 'jerbil/jerbil_service/base' require 'jerbil/jerbil_service/support' require 'jerbil/support' require 'jeeves/config' require 'jeeves/errors' require 'open3' # # == Jeeves Service # # Synopsis of the whole service # module Jscheduler # add generic class methods extend JerbilService::Support # inherit the Jeeves config file class Config < Jeeves::Config; end # === Jeeves Service class # # Description of how the service class works # class Base < JerbilService::Base # Document the constructor as required. # # pkey:: string - private key that must be used for # supervision calls (e.g. stop_callback) # options:: hash of options, preferably as created by Jeckyl. See documentation # on JerbilService::Base for assumed options re logging etc # # Note that this method should be super'd before any local initialization # def initialize(pkey, options) @tuner = options[:tuner] @device = '/dev/video0' # set this to the device from which the tuner streams # do things that cannot be done when $SAFE > 0 # the symbol should be the app name and must correspond to a service in /etc/services super(:jschedule, pkey, options) # DRb is now working, this process may have daemonized and $SAFE = 1 # the current priority, an integer where the higher the number the higher the priority @priority = 0 # save the pid for the tuner process if there is one @tuner_pid = nil @logger.verbose("Started scheduler for tuner #{@tuner}") end # # define additional class methods to make things happen # # tune_receiver is needed for each receiver to prepare that receiver for # a given channel. # # calls init_receiver to prepare the receiver itself # and then sets a timer to protect the tuner def tune_receiver(priority, params={}) # do not tune anything if there is a higher priority tune in progress raise Jeeves::TunerBusy if @priority > 0 && priority <= @priority # do the specific stuff for the given tuner and get back its pid (or not if there # is not process involved) self.stop_tuner(@tuner_pid) unless @tuner_pid.nil? @tuner_pid = self.init_tuner(params) if params[:duration] then # need to do this for a given time @priority = priority @thread = Thread.new do # use Jeckyl to ensure this is sensible! sleep(params[:duration]) # now clear up @priority = 0 self.stop_tuner(@tuner_pid) end else # no duration, so tune indefinitely # BUT what if there is a priority? Ignore it! @priority = 0 end #raise NotImplementedError # needs to do what ever the tuner requires to tune to the given channel # For Drb tuners this may involve a process running continuously, which # can be ended either after a certain time or when another call is made # to tune the receiver. # params include: # # * an identity for the channel as required by the tuner # * a priority - to allow recordings to overide casual viewing, for example # * a duration that enforces the priority and may also hold the tuner on. end # this will record the current receiver settings to a given file. It needs # to know the device from which to record and will then use FFMPEG to do # the recording def record(filename) end # play the current tuned channel on the primary X display def play end # stream the current tuned channel over the net using netcat def stream end # this will create an at job to record a programme at some time in the future def schedule(params={}) [:filename, :channel, :start, :duration].each do |param| raise Jeeves::MissingParam, "Schedule method must specify #{param}" unless params.has_key?(param) end mode = params[:mode] || :avi raise InvalidMode, "Tuner #{@tuner} does not have mode: #{mode}" unless Schedulers[@tuner].has_key?(mode) scheduler = Schedulers[@tuner][mode] startParam = params[:start].strftime("%H:%M %b %d") command = "#{scheduler} #{params[:channel]} #{params[:duration]} #{params[:filename]}" @logger.debug "About to schedule: #{command}" # need open3 to get the full communication with at inpipe, outpipe, errpipe = Open3.popen3("/usr/bin/at #{startParam}") inpipe.puts command inpipe.close response = errpipe.readlines outpipe.close errpipe.close # get the job id and return it to the caller jobnum = nil job_re = /job (\d+) at/ response.each do |resp| @logger.debug "Response includes: " + resp if results = job_re.match(resp) then @logger.debug "Recognised Job: " + results[1] jobnum = results[1].clone end end @logger.info "Scheduled recording on #{params[:channel]} at #{startParam} for #{params[:duration]} with job no: #{jobnum}" # set a wol timer here return jobnum rescue Jeeves::JeevesError raise rescue @logger.exception($!) return '0' end # deletes the at job with the given job number # assume that the given jobnum is valid def delete_schedule(jobnum) begin @logger.info "Removing job: #{jobnum}" system("/usr/bin/atrm #{jobnum.to_s}") rescue @logger.exception($!) end end # do not redefine the superclass methods unless you are absolutely sure you know # what you are doing. protected end end