Bundles
=======

The code from the wizard should move to their respective components,
and should be sent as bundles from the manager to the administration interface.
The components itself will, through the main wizard object have a reference
to the administration medium and using that it can easily call remote methods,
the main (only?) usage will be running code on a connected worker.

I'd suggest that a bundle is specified in the xml file, for instance:
<registry>
  <components>
    <component type="video-test"
               base="flumotion/component/producers/videotest">
<!-- base will make the manager look for functionality code in agreed-on paths
     e.g. admin/gtk.py will be looked for under this base for the GTK
     admin code -->
      <!-- additionally, entry points can be overridden, both path and
           entry function
           location is relative to base -->
      <entries>
        <entry type="admin/gtk"
               location="admin/gtk.py"
               function="..." />

        <entry type="component"
               location="videotest.py"
               function="createComponent />
       </entries>
    </component>
  </components>

  <bundles>
    <bundle name="component-videotest">
      <dependencies>
        <dependency name="component"/>
      </dependencies>
      <directories>
        <directory name="flumotion/components/producers/videotest">
          <filename location="videotest.py"/>
        </directory>
      </directoies>
    </bundle>
    <bundle name="glade-videotest">
      <directories>
        <directory name="flumotion/components/producers/videotest">
          <filename location="gtk/videotest.glade" relative="gtk/videotest.glade"/>
        </directory>
      </directories>
    </bundle>

    <bundle name="admin-gtk-videotest">
      <dependencies>
        <dependency name="base"/>
        <dependency name="glade-videotest"/>
      </dependencies>
      <directories>
        <directory name="flumotion/components/producers/videotest">
          <filename location="gtk/wizard.py"/>
        </directory>
      </directories>
    </bundle>

    <bundle name="wizard-gtk-videotest">
      <dependencies>
        <dependency name="base"/>
        <dependency name="glade-videotest"/>
      </dependencies>
      <directories>
        <directory name="flumotion/components/producers/videotest">
          <filename location="gtk/admin.py"/>
        </directory>
      </directories>
    </bundle>
  </bundles>
</registry>

Notes:

* The bundle name must be unique
* A file can be in only one bundle
* <directory> must have a name attribute, which is the base
  directory where all components are loaded from, it should
  be relative to the python root, eg always start with flumotion/
* i' should not in any ways be tied to python code, eg glade or images can
  also be included

TODO:
  Markers for wizard steps, currently hard coded to gtk/wizard.
  Name of wizard step mapped to a file in a bundle

Sending over a bundle
============================

- admin calls remote method getBundleEntryByType(type)
  where type is e.g. admin/gtk
  which returns a filename, method
- admin client calls remote method getBundleSumsByFile(filename)
- manager returns the name of the actual bundle,
  and a dict of bundle names to sums
- admin client verifies md5sum and calls getBundleByName(name) for each
  of the updated md5sums
- the bundle is unzipped and __path__ is set properly.
- admin imports the module and calls entry point

Note: to speed up the process, the answer to the request for a bundle might
send over a dictionary of bundle names and md5sums in the first go.

Creating a bundle
=================

This is only done on request to the manager.
Bundles are not created unless they're needed.

1. On startup, manager hands bundles configuration to bundle manager

2. Bundle manager creates a bundler for each bundle registered.

3. Manager gets request for a bundle's md5sum and hands it to the corresponding
   bundler (bundler.md5sum())

4. This is the first time a bundler actually looks at the files.  It notices
   the bundle is "out of date" since it was never actually created.  It
   creates the zip file in memory, and calculates the md5sum.

5. Bundle manager returns this md5sum

6. Bundle manager gets request for the zip file and calls bundler.zip()

7. Bundle manager returns zip file

Note: bundle manager could keep size statistics and last use statistics for
each bundler, so that we could set an upper limit on the memory use.

Note: we discussed having bundles create __init__.py files automatically on
      either bundling or unbundling.  In practice the rules for doing this
      would a) be very vague (what's the difference between a .py script
      and a .py file part of a package in some other bundle ?) and not
      actually solve a problem since just setting __path__ correctly makes
      imports work as they should.

Requesting bundles
==================

In BaseHeaven: (which does not exist yet)

remote_getBundle(name, filename, pythonName)

(and getBundle in BaseMedium)

getBundle does the dependency checking and fetches all bundles that
the request bundle depends on.


Public API
==========

## Manager side ##
class BaseHeaven:
  # Used from registry
  def addBundle(name, deps, files)
  def getBundleName(filename, pythonName)
  def getBundle(bundleName, filename, pythonName)
     return (bundle, deps) or (None, None)
  def remote_getBundle(name, filename, pythonName)

## Client side ##
class BaseMedium:
  # XXX: public or private?
  def getBundle(name, filename, pythonName)
  def remote_runBundle(bundleName, filename, objectName, *args, **kwargs)
    # to implement, base it on flumotion.worker.worker.remote_runCode
  def runBundle(bundleName, filename, objectName, *args, **kwargs):

   XXX: Perhaps remote_runCode, currently in worker (move to Base) should
        take an additional filename argument, so tracebacks can contain
        the name of the loaded module
        now we're always been run string segments, but in the future we
        will mostly run files (filename is passed when using execfile
        instead of exec)
	Otherwise we need a way of running a filename instead of a piece of
	code, maybe even deprecate the current method.

    Current definition:
      def remote_runCode(self, codeSegment, objectName, *args, **kwargs):
    Proposed new:
      def remote_runCode(self, filename, objectName, *args, **kwargs):
    Or maybe just make it internal to runBundle

This covers the two use cases:

1. Run a bundle locally
2. Run a bundle remotely (combined with workerCallRemote or
   componentCallRemote)

Running a component
===================

(manager)

1. When all dependencies are started, call WorkerHeaven.startComponent

2. It calls workerAvatar.start(name, type, config)

(worker)

3. In the worker, spawn a new job, fork etc

4. call runBundle on the BaseMedium

5. runBundle requests the bundle and all dependencies

6. it runs the bundle inside the job

Running probe code
==================

1. Name for the bundle is hard coded in the wizard step itself,
   say probe-soundcard and a hard coded entry point

2. WizardStep calls (through Wizard or directly) remote_runBundle, through
   workerCallRemote

Running wizard step
===================

1. take python name (eg, flumotion.component.producer.videotest) and
   add gtk.wizard, run that code with a specific entry code

2. Send it to remote_runBundle

Running admin configuration
===========================

TODO
