commit e82a264b9c5f51c4eb0d1cfffb37d5628c41bc88
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Sun Aug 15 23:12:13 2010 +0200

    [evol] added session management to the cyborg server (refs #31)

diff --git a/bin/mapmaker b/bin/mapmaker
index d0800ee..d136283 100755
--- a/bin/mapmaker
+++ b/bin/mapmaker
@@ -69,6 +69,7 @@ module CyborgHood
 
         class Zones
           include CyborgServerInterface
+          include CyborgServerStatefulInterface
 
           def initialize
             @dns = Services::DNS::System.new
@@ -79,15 +80,19 @@ module CyborgHood
           end
 
           def method_missing(method_name, *args)
-            if api_methods.include?(method_name.to_s)
-              Zone.new(method_name.to_s)
+            session = args.shift
+
+            zone_name = method_name.to_s
+            if api_methods.include?(zone_name)
+              resource_key = "dns/zones/#{zone_name}"
+              session.store.get(resource_key) { Zone.new(zone_name) }
             else
               super
             end
           end
 
           class Zone < Services::DNS::Zone
-            include CyborgServerEmbededInterface
+            include CyborgServerEmbeddedInterface
           end
         end
       end
diff --git a/lib/cyborghood/cyborg/interface.rb b/lib/cyborghood/cyborg/interface.rb
index 85ba866..a5a3852 100644
--- a/lib/cyborghood/cyborg/interface.rb
+++ b/lib/cyborghood/cyborg/interface.rb
@@ -27,13 +27,16 @@ module CyborgHood
     end
   end
 
-  module CyborgServerEmbededInterface
+  module CyborgServerEmbeddedInterface
     def self.included(base)
       base.class_eval("include CyborgServerInterfaceBase")
       base.export_parent_methods
     end
   end
 
+  module CyborgServerStatefulInterface
+  end
+
   module CyborgServerInterfaceBase
     NODE_PATTERN = "((?:\/|(?:\/[a-zA-Z0-9._]+)+[?=]?))"
 
@@ -82,6 +85,7 @@ module CyborgHood
     def api_methods
       methods = self.class.exported_methods
       methods += self.class.public_instance_methods(false) if self.class.auto_export_public_instance_methods
+      methods -= ["initialize", "__destroy", "method_missing"]
       methods & self.methods
     end
 
@@ -89,7 +93,7 @@ module CyborgHood
       (api_klasses.keys + api_methods).sort
     end
 
-    def find_node_action(node_name)
+    def find_node_action(session, node_name)
       node_name.gsub!(/^\//, "")
       next_node_name, other_nodes_names = node_name.split('/', 2)
 
@@ -103,16 +107,16 @@ module CyborgHood
             # cannot use method(), as this method may not exist at all (but still
             # be usuable through metaprogramming
             return lambda do |*args|
-              r = self.send(next_node_name, *args)
+              r = child_node(next_node_name, session, *args)
               # dynamic tree construction: method may return a node
-              if r.class.ancestors.include? CyborgHood::CyborgServerInterfaceBase
+              if r.is_a? CyborgHood::CyborgServerInterfaceBase
                 r.api_nodes
               else
                 r
               end
             end
           end
-          next_node = self.send(next_node_name)
+          next_node = child_node(next_node_name, session)
         else
           # unknown method
           return
@@ -124,21 +128,26 @@ module CyborgHood
       end
 
       # search deeper
-      if next_node.class.ancestors.include? CyborgHood::CyborgServerInterfaceBase
-        next_node.find_node_action(other_nodes_names)
+      if next_node.is_a? CyborgHood::CyborgServerInterfaceBase
+        next_node.find_node_action(session, other_nodes_names)
       else
         # it is not a node, so there are no children
         return
       end
     end
 
+    def child_node(next_node_name, session, *args)
+      args.unshift session if self.is_a? CyborgHood::CyborgServerStatefulInterface
+      self.send(next_node_name, *args)
+    end
+
     def has_node?(cmd)
-      not find_node_action(cmd).nil?
+      not find_node_action(nil, cmd).nil?
     end
 
     # preliminary incoming message handling
-    def call(cmd, data)
-      action = find_node_action(cmd)
+    def call(session, cmd, data)
+      action = find_node_action(session, cmd)
       return "551 unknown node" if action.nil?
 
       if data.nil?
diff --git a/lib/cyborghood/cyborg/server.rb b/lib/cyborghood/cyborg/server.rb
index ba95d62..cfa6d9d 100644
--- a/lib/cyborghood/cyborg/server.rb
+++ b/lib/cyborghood/cyborg/server.rb
@@ -92,6 +92,7 @@ module CyborgHood
       @split_data_mode = false
       @split_data_cmd = nil
       @split_data = []
+      @session = Session.new
     end
 
     def send_line(msg)
@@ -144,7 +145,7 @@ module CyborgHood
 
     def receive_command(cmd, data = nil)
       logger.debug "Executing command '#{cmd}' [#{identifier}]"
-      send_line @interface.call(cmd, data)
+      send_line @interface.call(@session, cmd, data)
     end
 
     def receive_error(msg)
@@ -153,6 +154,7 @@ module CyborgHood
 
     def unbind
       logger.debug "Conversation finished with #{identifier}"
+      @session.clear
     end
 
     protected
@@ -218,4 +220,30 @@ module CyborgHood
       EmptyInterface.instance
     end
   end
+
+  class Session
+    attr_reader :store
+
+    def initialize
+      @store = Store.new
+    end
+
+    def clear
+      @store.values.each do |obj|
+        obj.__destroy if obj.respond_to? :__destroy
+      end
+      @store.clear
+    end
+
+    class Store < Hash
+      def get(key)
+        obj = self[key]
+        if obj.nil? and block_given?
+          obj = yield
+          self[key] = obj
+        end
+        obj
+      end
+    end
+  end
 end
diff --git a/lib/cyborghood/services/dns.rb b/lib/cyborghood/services/dns.rb
index 0758719..aed8fdd 100644
--- a/lib/cyborghood/services/dns.rb
+++ b/lib/cyborghood/services/dns.rb
@@ -76,8 +76,6 @@ module CyborgHood
           @temp_file = nil
 
           @filename = @config.dns.master_zone_pattern.gsub("#ZONE#", @zone)
-
-          at_exit {cleanup_temp}
         end
 
         def content
@@ -146,6 +144,10 @@ module CyborgHood
           raise CyberError.new(:unrecoverable, "services/dns", "zone activation failed") unless $?.success?
         end
 
+        def __destroy
+          cleanup_temp
+        end
+
         protected
 
         def backup_filename
