docs: def mainloop(self):
          """ The main loop.
          """
          # Print field definitions?
          if self.options.help_fields:
              self.parser.print_help()
              print_help_fields()
              sys.exit(1)
  
          # Print usage if no conditions are provided
          if not self.args:
              self.parser.error("No filter conditions given!")
          self.check_for_connection()
  
          # Check special action options
          actions = []
          if self.options.ignore:
              actions.append(Bunch(name="ignore", method="ignore", label="IGNORE" if int(self.options.ignore) else "HEED",
                  help="commands on torrent", interactive=False, args=(self.options.ignore,)))
          if self.options.prio:
              actions.append(Bunch(name="prio", method="set_prio", label="PRIO" + str(self.options.prio),
                  help="for torrent", interactive=False, args=(self.options.prio,)))
  
          # Check standard action options
          # TODO: Allow certain combinations of actions (like --tag foo --stop etc.)
          #       How do we get a sensible order of execution?
          for action_mode in self.ACTION_MODES:
              if getattr(self.options, action_mode.name):
                  if actions:
                      self.parser.error("Options --%s and --%s are mutually exclusive" % (
                          ", --".join(i.name.replace('_', '-') for i in actions),
                          action_mode.name.replace('_', '-'),
                      ))
                  if action_mode.argshelp:
                      action_mode.args = (getattr(self.options, action_mode.name),)
                  actions.append(action_mode)
          if not actions and self.options.flush:
              actions.append(Bunch(name="flush", method="flush", label="FLUSH",
                  help="flush session data", interactive=False, args=()))
              self.options.flush = False # No need to flush twice
          if any(i.interactive for i in actions):
              self.options.interactive = True
  
          # Reduce results according to index range
          selection = None
          if self.options.select:
              try:
                  if '-' in self.options.select:
                      selection = tuple(int(i or default, 10) for i, default in
                          zip(self.options.select.split('-', 1), ("1", "-1")))
                  else:
                      selection = 1, int(self.options.select, 10)
              except (ValueError, TypeError), exc:
                  self.fatal("Bad selection '%s' (%s)" % (self.options.select, exc))
  
  #        print repr(config.engine)
  #        config.engine.open()
  #        print repr(config.engine)
  
          # Preparation steps
          if self.options.fast_query != '=':
              config.fast_query = int(self.options.fast_query)
          raw_output_format = self.options.output_format
          default_output_format = "default"
          if actions:
              default_output_format = "action_cron" if self.options.cron else "action"
          self.validate_output_format(default_output_format)
          sort_key = self.validate_sort_fields()
          matcher = matching.ConditionParser(engine.FieldDefinition.lookup, "name").parse(self.args)
          self.LOG.debug("Matcher is: %s" % matcher)
  
          # Detach to background?
          # This MUST happen before the next step, when we connect to the torrent client
          if self.options.detach:
              config.engine.load_config()
              daemon_log = os.path.join(config.config_dir, "log", "rtcontrol.log")
              osmagic.daemonize(logfile=daemon_log if os.path.exists(os.path.dirname(daemon_log)) else None)
              time.sleep(.05) # let things settle a little
  
          # View handling
          if self.options.append_view and self.options.alter_view:
              self.fatal("You cannot combine --append-view with --alter-view")
  
          if self.options.modify_view:
              if self.options.from_view or self.options.to_view:
                  self.fatal("You cannot combine --modify-view with --from-view or --to-view")
              self.options.from_view = self.options.to_view = self.options.modify_view
  
          # Find matching torrents
          view = config.engine.view(self.options.from_view, matcher)
          matches = list(view.items())
          orig_matches = matches[:]
          matches.sort(key=sort_key, reverse=self.options.reverse_sort)
  
          if self.options.anneal:
              if not self.options.quiet and set(self.options.anneal).difference(
                                            set(['invert', 'unique'])):
                  if self.options.from_view not in (None, 'default'):
                      self.LOG.warn("Mixing --anneal with a view other than 'default' might yield unexpected results!")
                  if int(config.fast_query):
                      self.LOG.warn("Using --anneal together with the query optimizer might yield unexpected results!")
              for mode in self.options.anneal:
                  if self.anneal(mode, matches, orig_matches):
                      matches.sort(key=sort_key, reverse=self.options.reverse_sort)
  
          if selection:
              matches = matches[selection[0]-1:selection[1]]
  
          if not matches:
              # Think "404 NOT FOUND", but then exit codes should be < 256
              self.return_code = 44
  
          # Build header stencil
          stencil = None
          if self.options.column_headers and self.plain_output_format and matches:
              stencil = fmt.to_console(formatting.format_item(
                  self.options.output_format, matches[0], self.FORMATTER_DEFAULTS)).split('\t')
  
          # Tee to ncurses view, if requested
          if self.options.tee_view and (self.options.to_view or self.options.view_only):
              self.show_in_view(view, matches)
  
          # Generate summary?
          summary = FieldStatistics(len(matches))
          if self.options.stats or self.options.summary:
              for field in self.get_output_fields():
                  try:
                      0 + getattr(matches[0], field)
                  except (TypeError, ValueError, IndexError):
                      summary.total[field] = ''
                  else:
                      for item in matches:
                          summary.add(field, getattr(item, field))
  
          def output_formatter(templ, namespace=None):
              "Output formatting helper"
              full_ns = dict(
                  version=self.version,
                  proxy=config.engine.open(),
                  view=view,
                  query=matcher,
                  matches=matches,
                  summary=summary
              )
              full_ns.update(namespace or {})
              return formatting.expand_template(templ, full_ns)
  
          # Execute action?
          if actions:
              action = actions[0] # TODO: loop over it
              self.LOG.log(logging.DEBUG if self.options.cron else logging.INFO, "%s %s %d out of %d torrents." % (
                  "Would" if self.options.dry_run else "About to", action.label, len(matches), view.size(),
              ))
              defaults = {"action": action.label}
              defaults.update(self.FORMATTER_DEFAULTS)
  
              if self.options.column_headers and matches:
                  self.emit(None, stencil=stencil)
  
              # Perform chosen action on matches
              template_args = [formatting.preparse("{{#tempita}}" + i if "{{" in i else i) for i in action.args]
              for item in matches:
                  if not self.prompt.ask_bool("%s item %s" % (action.label, item.name)):
                      continue
                  if (self.options.output_format and not self.options.view_only
                          and str(self.options.output_format) != "-"):
                      self.emit(item, defaults, to_log=self.options.cron)
  
                  args = tuple([output_formatter(i, namespace=dict(item=item)) for i in template_args])
  
                  if self.options.dry_run:
                      if self.options.debug:
                          self.LOG.debug("Would call action %s(*%r)" % (action.method, args))
                  else:
                      getattr(item, action.method)(*args)
                      if self.options.flush:
                          item.flush()
                      if self.options.view_only:
                          show_in_client = lambda x: config.engine.open().log(xmlrpc.NOHASH, x)
                          self.emit(item, defaults, to_log=show_in_client)
  
          # Show in ncurses UI?
          elif not self.options.tee_view and (self.options.to_view or self.options.view_only):
              self.show_in_view(view, matches)
  
          # Execute OS commands?
          elif self.options.call or self.options.spawn:
              if self.options.call and self.options.spawn:
                  self.fatal("You cannot mix --call and --spawn")
  
              template_cmds = []
              if self.options.call:
                  template_cmds.append([formatting.preparse("{{#tempita}}" + self.options.call)])
              else:
                  for cmd in self.options.spawn:
                      template_cmds.append([formatting.preparse("{{#tempita}}" + i if "{{" in i else i)
                                            for i in shlex.split(str(cmd))])
  
              for item in matches:
                  cmds = [[output_formatter(i, namespace=dict(item=item)) for i in k] for k in template_cmds]
                  cmds = [[i.encode('utf-8') if isinstance(i, unicode) else i for i in k] for k in cmds]
  
                  if self.options.dry_run:
                      self.LOG.info("Would call command(s) %r" % (cmds,))
                  else:
                      for cmd in cmds:
                          if self.options.call:
                              logged_cmd = cmd[0]
                          else:
                              logged_cmd = '"%s"' % ('" "'.join(cmd),)
                          if self.options.verbose:
                              self.LOG.info("Calling: %s" % (logged_cmd,))
                          try:
                              if self.options.call:
                                  subprocess.check_call(cmd[0], shell=True)
                              else:
                                  subprocess.check_call(cmd)
                          except subprocess.CalledProcessError, exc:
                              raise error.UserError("Command failed: %s" % (exc,))
                          except OSError, exc:
                              raise error.UserError("Command failed (%s): %s" % (logged_cmd, exc,))
  
          # Dump as JSON array?
          elif self.options.json:
              json_data = matches
              if raw_output_format:
                  json_fields = raw_output_format.split(',')
                  json_data = [dict([(name, getattr(i, name)) for name in json_fields])
                               for i in matches]
              json.dump(json_data, sys.stdout, indent=2, separators=(',', ': '),
                        sort_keys=True, cls=pymagic.JSONEncoder)
              sys.stdout.write('\n')
              sys.stdout.flush()
  
          # Show via template?
          elif self.options.output_template:
              output_template = self.options.output_template
              if not output_template.startswith("file:"):
                  output_template = "file:" + output_template
  
              sys.stdout.write(output_formatter(output_template))
              sys.stdout.flush()
  
          # Show on console?
          elif self.options.output_format and str(self.options.output_format) != "-":
              if not self.options.summary:
                  line_count = 0
                  for item in matches:
                      # Emit a header line every 'output_header_frequency' lines
                      if self.options.column_headers and line_count % config.output_header_frequency == 0:
                          self.emit(None, stencil=stencil)
  
                      # Print matching item
                      line_count += self.emit(item, self.FORMATTER_DEFAULTS)
  
              # Print summary?
              if matches and summary:
                  self.emit(None, stencil=stencil)
                  self.emit(summary.total, item_formatter=lambda i: i.rstrip() + " [SUM of %d item(s)]" % len(matches))
                  self.emit(summary.min, item_formatter=lambda i: i.rstrip() + " [MIN of %d item(s)]" % len(matches))
                  self.emit(summary.average, item_formatter=lambda i: i.rstrip() + " [AVG of %d item(s)]" % len(matches))
                  self.emit(summary.max, item_formatter=lambda i: i.rstrip() + " [MAX of %d item(s)]" % len(matches))
  
              self.LOG.info("Dumped %d out of %d torrents." % (len(matches), view.size(),))
          else:
              self.LOG.info("Filtered %d out of %d torrents." % (len(matches), view.size(),))
  
          if self.options.debug and 0:
              print '\n' + repr(matches[0])
              print '\n' + repr(matches[0].files)
  
          # XMLRPC stats
          self.LOG.debug("XMLRPC stats: %s" % config.engine._rpc)