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
#---------------------------------------------------------------
|