summaryrefslogtreecommitdiffstats
path: root/lib/blink/transaction.rb
blob: 56d1268b5461282a4b076a67446b20819bf90feb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/usr/local/bin/ruby -w

# $Id$

# the class that actually walks our object/state tree, collects the changes,
# and performs them

# there are two directions of walking:
#   - first we recurse down the tree and collect changes
#   - then we walk back up the tree through 'refresh' after the changes

require 'blink'
require 'blink/statechange'

#---------------------------------------------------------------
module Blink
class Transaction
    attr_accessor :toplevel

    #---------------------------------------------------------------
    # a bit of a gross hack; a global list of objects that have failed to sync,
    # so that we can verify during later syncs that our dependencies haven't
    # failed
    def Transaction.init
        @@failures = Hash.new(0)
        @@changed = []
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    # for now, just store the changes for executing linearly
    # later, we might execute them as we receive them
    def change(change)
        @changes.push change
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    # i don't need to worry about ordering, because it's not possible to specify
    # an object as a dependency unless it's already been mentioned within the language
    # thus, an object gets defined, then mentioned as a dependency, and the objects
    # are synced in that order automatically
    def evaluate
        Blink.notice "executing %s changes or transactions" % @changes.length

        @changes.each { |change|
            if change.is_a?(Blink::StateChange)
                change.transaction = self
                begin
                    change.forward
                    #@@changed.push change.state.parent
                rescue => detail
                    Blink.error("%s failed: %s" % [change,detail])
                    # at this point, we would normally do error handling
                    # but i haven't decided what to do for that yet
                    # so just record that a sync failed for a given object
                    #@@failures[change.state.parent] += 1
                    # this still could get hairy; what if file contents changed,
                    # but a chmod failed?  how would i handle that error? dern
                end
            elsif change.is_a?(Blink::Transaction)
                # yay, recursion
                change.evaluate
            else
                raise "Transactions cannot handle objects of type %s" % child.class
            end
        }

        if @toplevel # if we're the top transaction, perform the refreshes
            Blink::Event.process
            #notifies = @@changed.uniq.collect { |object|
            #    object.notify
            #}.flatten.uniq

            # now we have the entire list of objects to notify
        else
            Blink.notice "I'm not top-level"
            # these are the objects that need to be refreshed
            #return @refresh.uniq
        end
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    # this should only be called by a Blink::Container object now
    # and it should only receive an array
    def initialize(tree)
        @tree = tree
        @toplevel = false

        @triggered = Hash.new(0)

        # of course, this won't work on the second run
        unless defined? @@failures
            @toplevel = true
            self.class.init
        end
        # change collection is in-band, and message generation is out-of-band
        # of course, exception raising is also out-of-band
        @changes = @tree.collect { |child|
            # these children are all Blink::Type instances
            # not all of the children will return a change, and Containers
            # return transactions
            child.evaluate
        }.flatten.reject { |child|
            child.nil? # remove empties
        }
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    def rollback
        @changes.each { |change|
            if change.is_a?(Blink::StateChange)
                next unless change.run
                #change.transaction = self
                begin
                    change.backward
                    #@@changed.push change.state.parent
                rescue => detail
                    Blink.error("%s rollback failed: %s" % [change,detail])
                    # at this point, we would normally do error handling
                    # but i haven't decided what to do for that yet
                    # so just record that a sync failed for a given object
                    #@@failures[change.state.parent] += 1
                    # this still could get hairy; what if file contents changed,
                    # but a chmod failed?  how would i handle that error? dern
                end
            elsif change.is_a?(Blink::Transaction)
                # yay, recursion
                change.rollback
            else
                raise "Transactions cannot handle objects of type %s" % child.class
            end
        }
    end
    #---------------------------------------------------------------

    #---------------------------------------------------------------
    def triggered(sub)
        @triggered[sub] += 1
    end
    #---------------------------------------------------------------
end
end
#---------------------------------------------------------------