rubysync 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. data/.DS_Store +0 -0
  2. data/.project +17 -0
  3. data/COPYING +339 -0
  4. data/HISTORY.txt +8 -0
  5. data/Manifest.txt +218 -0
  6. data/README.txt +67 -0
  7. data/Rakefile +31 -0
  8. data/bin/.DS_Store +0 -0
  9. data/bin/rubysync +19 -12
  10. data/examples/.DS_Store +0 -0
  11. data/examples/ar_client_webapp/log/development.log +753 -0
  12. data/examples/ar_client_webapp/log/production.log +0 -0
  13. data/examples/ar_client_webapp/log/server.log +0 -0
  14. data/examples/ar_client_webapp/log/test.log +0 -0
  15. data/examples/ar_client_webapp/public/.htaccess +40 -0
  16. data/examples/ar_client_webapp/tmp/sessions/ruby_sess.e2e3c63a67baef6d +0 -0
  17. data/examples/ar_webapp/.DS_Store +0 -0
  18. data/examples/ar_webapp/app/.DS_Store +0 -0
  19. data/examples/ar_webapp/app/views/.DS_Store +0 -0
  20. data/examples/ar_webapp/app/views/people/.DS_Store +0 -0
  21. data/examples/ar_webapp/log/development.log +5518 -0
  22. data/examples/ar_webapp/log/production.log +0 -0
  23. data/examples/ar_webapp/log/server.log +0 -0
  24. data/examples/ar_webapp/log/test.log +2178 -0
  25. data/examples/ar_webapp/public/.htaccess +40 -0
  26. data/examples/ar_webapp/tmp/sessions/ruby_sess.2295696b0af5f6dd +0 -0
  27. data/examples/ar_webapp/tmp/sessions/ruby_sess.26687aeb19c87669 +0 -0
  28. data/examples/ar_webapp/tmp/sessions/ruby_sess.2855a3b0c8ea840b +0 -0
  29. data/examples/ar_webapp/tmp/sessions/ruby_sess.45d2d48a8330ff28 +0 -0
  30. data/examples/ar_webapp/tmp/sessions/ruby_sess.7366b840f4ce9f12 +0 -0
  31. data/examples/ar_webapp/tmp/sessions/ruby_sess.b2fc3f2e6d8ae555 +0 -0
  32. data/examples/ar_webapp/tmp/sessions/ruby_sess.b6bf8470a62c02b0 +0 -0
  33. data/examples/my_ims/.DS_Store +0 -0
  34. data/gemspec +48 -0
  35. data/lib/.DS_Store +0 -0
  36. data/lib/net/.DS_Store +0 -0
  37. data/lib/ruby_sync/connectors/active_record_association_handler.rb +66 -0
  38. data/lib/ruby_sync/connectors/active_record_connector.rb +14 -62
  39. data/lib/ruby_sync/connectors/active_record_event_handler.rb +47 -0
  40. data/lib/ruby_sync/connectors/base_connector.rb +139 -36
  41. data/lib/ruby_sync/connectors/connector_event_processing.rb +18 -0
  42. data/lib/ruby_sync/connectors/csv_file_connector.rb +3 -0
  43. data/lib/ruby_sync/connectors/file_connector.rb +1 -0
  44. data/lib/ruby_sync/connectors/memory_connector.rb +2 -104
  45. data/lib/ruby_sync/connectors/xml_connector.rb +98 -0
  46. data/lib/ruby_sync/event.rb +7 -1
  47. data/lib/ruby_sync/util/utilities.rb +27 -16
  48. data/lib/ruby_sync.rb +10 -1
  49. data/lib/rubysync.rb +19 -0
  50. data/nbproject/private/private.properties +3 -0
  51. data/nbproject/project.properties +8 -0
  52. data/nbproject/project.xml +16 -0
  53. data/rubysync.tmproj +348 -0
  54. data/test/.DS_Store +0 -0
  55. data/test/ruby_sync_test.rb +8 -1
  56. data/test/tc_xml_connectors.rb +47 -0
  57. data/test/test_active_record_vault.rb +17 -14
  58. data/test/test_base_connector.rb +38 -0
  59. data/test/test_ldap_changelog.rb +97 -0
  60. data/test/test_ldap_connector.rb +2 -48
  61. data/test/test_memory_connectors.rb +8 -3
  62. data/test/test_rubysync.rb +28 -0
  63. data/test/ts_rubysync.rb +5 -3
  64. metadata +129 -169
@@ -0,0 +1,40 @@
1
+ # General Apache options
2
+ AddHandler fastcgi-script .fcgi
3
+ AddHandler cgi-script .cgi
4
+ Options +FollowSymLinks +ExecCGI
5
+
6
+ # If you don't want Rails to look in certain directories,
7
+ # use the following rewrite rules so that Apache won't rewrite certain requests
8
+ #
9
+ # Example:
10
+ # RewriteCond %{REQUEST_URI} ^/notrails.*
11
+ # RewriteRule .* - [L]
12
+
13
+ # Redirect all requests not available on the filesystem to Rails
14
+ # By default the cgi dispatcher is used which is very slow
15
+ #
16
+ # For better performance replace the dispatcher with the fastcgi one
17
+ #
18
+ # Example:
19
+ # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
20
+ RewriteEngine On
21
+
22
+ # If your Rails application is accessed via an Alias directive,
23
+ # then you MUST also set the RewriteBase in this htaccess file.
24
+ #
25
+ # Example:
26
+ # Alias /myrailsapp /path/to/myrailsapp/public
27
+ # RewriteBase /myrailsapp
28
+
29
+ RewriteRule ^$ index.html [QSA]
30
+ RewriteRule ^([^.]+)$ $1.html [QSA]
31
+ RewriteCond %{REQUEST_FILENAME} !-f
32
+ RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
33
+
34
+ # In case Rails experiences terminal errors
35
+ # Instead of displaying this message you can supply a file here which will be rendered instead
36
+ #
37
+ # Example:
38
+ # ErrorDocument 500 /500.html
39
+
40
+ ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
Binary file
data/gemspec ADDED
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2007 Ritchie Young. All rights reserved.
2
+ #
3
+ # This file is part of RubySync.
4
+ #
5
+ # RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
7
+ #
8
+ # RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9
+ # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
+ #
11
+ # You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
12
+ # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
13
+
14
+
15
+ require 'rubygems'
16
+
17
+ SPEC = Gem::Specification.new do |s|
18
+ s.name = "rubysync"
19
+ s.version = "0.0.3"
20
+ s.rubyforge_project = 'rubysync'
21
+ s.author = "Ritchie Young"
22
+ s.email = "ritchiey@gmail.com"
23
+ s.homepage = "http://rubysync.org"
24
+ #s.platform = "Gem::Platform::RUBY"
25
+ s.summary = "Event driven identity synchronization engine"
26
+ candidates = Dir.glob "{bin,docs,lib,test,examples}/**/*"
27
+ s.files = candidates.delete_if do |item|
28
+ item.include?("rubysync.tmproj") ||
29
+ item.include?(".svn") ||
30
+ item.include?(".project") ||
31
+ item.include?('.DS_Store') ||
32
+ item.include?('tmp') ||
33
+ item.include?('log')
34
+ end
35
+ s.bindir = 'bin'
36
+ s.require_path = 'lib'
37
+ s.executables = ['rubysync']
38
+ s.default_executable = 'rubysync'
39
+ s.autorequire = 'ruby_sync.rb'
40
+ s.test_file = 'test/ts_rubysync.rb'
41
+ s.has_rdoc = true
42
+ #s.extra_rdoc_files = ["README"]
43
+ s.add_dependency "ruby-net-ldap", ">=0.0.4"
44
+ s.add_dependency "activesupport", ">=1.4.0"
45
+ s.add_dependency "activerecord", ">=1.15.3"
46
+ s.add_dependency "simpleconsole", ">=0.1.1"
47
+ s.add_dependency "xmlsimple", ">=1.0.11"
48
+ end
data/lib/.DS_Store ADDED
Binary file
data/lib/net/.DS_Store ADDED
Binary file
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby -w
2
+ #
3
+ # Copyright (c) 2007 Ritchie Young. All rights reserved.
4
+ #
5
+ # This file is part of RubySync.
6
+ #
7
+ # RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
9
+ #
10
+ # RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
11
+ # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
14
+ # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15
+
16
+
17
+ lib_path = File.dirname(__FILE__) + '/../lib'
18
+ $:.unshift lib_path unless $:.include?(lib_path) || $:.include?(File.expand_path(lib_path))
19
+
20
+ require 'ruby_sync'
21
+
22
+ module RubySync::Connectors::ActiveRecordAssociationHandler
23
+
24
+ def associate association, path
25
+ log.debug "Associating '#{association}' with '#{path}'"
26
+ ruby_sync_association.create :synchronizable_id=>path, :synchronizable_type=>ar_class.name,
27
+ :context=>association.context, :key=>association.key
28
+ end
29
+
30
+ def find_associated association
31
+ ruby_sync_association.find_by_context_and_key association.context, association.key
32
+ end
33
+
34
+ def path_for_association association
35
+ assoc = ruby_sync_association.find_by_context_and_key association.context, association.key
36
+ (assoc)? assoc.synchronizable_id : nil
37
+ end
38
+
39
+ def association_key_for context, path
40
+ record = ruby_sync_association.find_by_synchronizable_id_and_synchronizable_type_and_context path, model.to_s, context
41
+ record and record.key
42
+ end
43
+
44
+ def associations_for(path)
45
+ ruby_sync_association.find_by_synchronizable_id_and_synchronizable_type(path, model.to_s)
46
+ rescue ActiveRecord::RecordNotFound
47
+ return nil
48
+ end
49
+
50
+ def remove_association association
51
+ ruby_sync_association.find_by_context_and_key(association.context, association.key).destroy
52
+ rescue ActiveRecord::RecordNotFound
53
+ return nil
54
+ end
55
+
56
+ private
57
+
58
+ def ruby_sync_association
59
+ unless @ruby_sync_association
60
+ @ruby_sync_association = ::RubySyncAssociation
61
+ ::RubySyncAssociation.establish_connection(db_config)
62
+ end
63
+ @ruby_sync_association
64
+ end
65
+
66
+ end
@@ -66,6 +66,7 @@ module RubySync::Connectors
66
66
  self.class.ar_class model.to_s.camelize.constantize
67
67
  end
68
68
 
69
+
69
70
 
70
71
  def self.fields
71
72
  c = self.new
@@ -81,22 +82,14 @@ module RubySync::Connectors
81
82
  END
82
83
  end
83
84
 
84
-
85
- # Process each RubySyncEvent and then delete it from the db.
86
- def each_change
87
- ::RubySyncEvent.find(:all).each do |rse|
88
- event = RubySync::Event.new(rse.event_type, self, rse.trackable_id, nil, to_payload(rse))
89
- yield event
90
- ::RubySyncEvent.delete rse
85
+
86
+ def each_entry
87
+ ar_class.find :all do |record|
88
+ yield entry_from_active_record(record)
91
89
  end
92
90
  end
91
+
93
92
 
94
- # Create a hash suitable to use as rubysync event payload
95
- def to_payload ar_event
96
- ar_event.operations.map do |op|
97
- RubySync::Operation.new(op.operation.to_sym, op.field_name, op.values.map {|v| v.value})
98
- end
99
- end
100
93
 
101
94
  # Override default perform_add because ActiveRecord is different in that
102
95
  # the target path is ignored when adding a record. ActiveRecord determines
@@ -107,6 +100,7 @@ END
107
100
  populate(record, perform_operations(event.payload))
108
101
  log.info(record.inspect)
109
102
  record.save!
103
+ update_mirror record.id
110
104
  if is_vault?
111
105
  associate event.association, record.id
112
106
  end
@@ -114,7 +108,6 @@ END
114
108
  end
115
109
  rescue => ex
116
110
  log.warn ex
117
- #puts ex.backtrace.join("\n")
118
111
  return nil
119
112
  end
120
113
 
@@ -129,71 +122,30 @@ END
129
122
  def delete(path)
130
123
  ar_class.destroy path
131
124
  end
132
-
133
- # Implement vault functionality
134
-
135
- def associate association, path
136
- log.debug "Associating '#{association}' with '#{path}'"
137
- ruby_sync_association.create :synchronizable_id=>path, :synchronizable_type=>ar_class.name,
138
- :context=>association.context, :key=>association.key
139
- end
140
-
141
- def find_associated association
142
- ruby_sync_association.find_by_context_and_key association.context, association.key
143
- end
144
-
145
- def path_for_association association
146
- assoc = ruby_sync_association.find_by_context_and_key association.context, association.key
147
- (assoc)? assoc.synchronizable_id : nil
148
- end
149
-
150
- def association_key_for context, path
151
- record = ruby_sync_association.find_by_synchronizable_id_and_synchronizable_type_and_context path, model.to_s, context
152
- record and record.key
153
- end
154
-
155
- def associations_for(path)
156
- ruby_sync_association.find_by_synchronizable_id_and_synchronizable_type(path, model.to_s)
157
- rescue ActiveRecord::RecordNotFound
158
- return nil
159
- end
160
-
161
- def remove_association association
162
- ruby_sync_association.find_by_context_and_key(association.context, association.key).destroy
163
- rescue ActiveRecord::RecordNotFound
164
- return nil
165
- end
166
-
167
125
 
168
126
  def [](path)
169
- ar_class.find(path)
127
+ entry_from_active_record(ar_class.find(path))
170
128
  rescue ActiveRecord::RecordNotFound
171
129
  return nil
172
130
  end
173
131
 
174
132
  private
175
133
 
176
- def ruby_sync_association
177
- unless @ruby_sync_association
178
- @ruby_sync_association = ::RubySyncAssociation
179
- ::RubySyncAssociation.establish_connection(db_config)
180
- end
181
- @ruby_sync_association
182
- end
183
-
184
134
  def populate record, content
185
135
  ar_class.content_columns.each do |c|
186
136
  record[c.name] = content[c.name][0] if content[c.name]
187
137
  end
188
138
  end
189
139
 
190
- def create_operations_for_active_record record
191
- operations = record.class.content_columns.map do |col|
140
+ def entry_from_active_record record
141
+ entry = {}
142
+ record.class.content_columns.each do |col|
192
143
  key = col.name
193
144
  value = record.send key
194
- RubySync::Operation.new(:add, key.to_s, value) if key and value
145
+ entry[key.to_s] = value if key and value
195
146
  end
196
- operations.compact
147
+ entry
197
148
  end
149
+
198
150
  end
199
151
  end
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby -w
2
+ #
3
+ # Copyright (c) 2007 Ritchie Young. All rights reserved.
4
+ #
5
+ # This file is part of RubySync.
6
+ #
7
+ # RubySync is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
9
+ #
10
+ # RubySync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
11
+ # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License along with RubySync; if not, write to the
14
+ # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15
+
16
+
17
+ lib_path = File.dirname(__FILE__) + '/../lib'
18
+ $:.unshift lib_path unless $:.include?(lib_path) || $:.include?(File.expand_path(lib_path))
19
+
20
+ require 'ruby_sync'
21
+
22
+ module RubySync::Connectors::ActiveRecordEventHandler
23
+
24
+ # Disable mirror updates for incoming events
25
+ def update_mirror(path); end
26
+ def delete_from_mirror(path); end
27
+
28
+ # Process each RubySyncEvent and then delete it from the db.
29
+ def each_change
30
+ ::RubySyncEvent.find(:all).each do |rse|
31
+ event = RubySync::Event.new(rse.event_type, self, rse.trackable_id, nil, to_payload(rse))
32
+ yield event
33
+ ::RubySyncEvent.delete rse
34
+ end
35
+ end
36
+
37
+ # Create a hash suitable to use as rubysync event payload
38
+ def to_payload ar_event
39
+ ar_event.operations.map do |op|
40
+ RubySync::Operation.new(op.operation.to_sym, op.field_name, op.values.map {|v| v.value})
41
+ end
42
+ end
43
+
44
+
45
+
46
+
47
+ end
@@ -14,6 +14,8 @@
14
14
  # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15
15
 
16
16
  require 'ruby_sync/connectors/connector_event_processing'
17
+ require 'dbm'
18
+ require 'digest/md5'
17
19
 
18
20
  module RubySync::Connectors
19
21
  class BaseConnector
@@ -22,8 +24,30 @@ module RubySync::Connectors
22
24
  include ConnectorEventProcessing
23
25
 
24
26
  attr_accessor :once_only, :name, :is_vault
27
+ option :dbm_path
28
+
29
+ # set a default dbm path
30
+ def dbm_path() "#{base_path}/db/#{name}"; end
31
+
32
+ # Stores association keys indexed by path:association_context
33
+ def path_to_association_dbm_filename
34
+ dbm_path + "_path_to_assoc"
35
+ end
36
+
37
+ # Stores paths indexed by association_context:association_key
38
+ def association_to_path_dbm_filename
39
+ dbm_path + "_assoc_to_path"
40
+ end
41
+
42
+ # Stores a hash for each entry so we can tell when
43
+ # entries are added, deleted or modified
44
+ def mirror_dbm_filename
45
+ dbm_path + "_mirror"
46
+ end
25
47
 
26
48
  def initialize options={}
49
+ base_path # call this once to get the working directory before anything else
50
+ # in the connector changes the cwd
27
51
  options = self.class.default_options.merge(options)
28
52
  once_only = false
29
53
  self.name = options[:name]
@@ -48,20 +72,49 @@ module RubySync::Connectors
48
72
 
49
73
  # Override this to perform actions that must be performed the
50
74
  # when the connector starts running. (Eg, opening network connections)
51
- def started; end
75
+ def started
76
+ end
52
77
 
53
78
  # Subclasses must override this to
54
- # interface with the external system and generate events for every
55
- # entry in the scope.
56
- # These events are yielded to the passed in block to process.
79
+ # interface with the external system and generate entries for every
80
+ # entry in the scope passing the entry path (id) and its data (as a hash of arrays).
57
81
  # This method will be called repeatedly until the connector is
58
82
  # stopped.
59
- def each_entry; end
83
+ def each_entry
84
+ raise "Not implemented"
85
+ end
60
86
 
61
87
  # Subclasses must override this to interface with the external system
62
88
  # and generate an event for every change that affects items within
63
89
  # the scope of this connector.
64
- def each_change; end
90
+ # todo: Make the default behaviour to build a database of the key of
91
+ # each entry with a hash of the contents of the entry. Then to compare
92
+ # that against each entry to see if it has changed.
93
+ def each_change
94
+ DBM.open(self.mirror_dbm_filename) do |dbm|
95
+ # scan existing entries to see if any new or modified
96
+ each_entry do |path, entry|
97
+ digest = digest(entry)
98
+ unless stored_digest = dbm[path.to_s] and digest == stored_digest
99
+ operations = create_operations_for(entry)
100
+ yield RubySync::Event.add(self, path, nil, operations)
101
+ dbm[path.to_s] = digest
102
+ end
103
+ end
104
+
105
+ # scan dbm to find deleted
106
+ dbm.each do |key, stored_hash|
107
+ unless self[key]
108
+ yield RubySync::Event.delete(self, key)
109
+ dbm.delete key
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def digest(o)
116
+ Digest::MD5.hexdigest(Marshal.dump(o))
117
+ end
65
118
 
66
119
  # Override this to perform actions that must be performed when
67
120
  # the connector exits (eg closing network conections).
@@ -71,12 +124,16 @@ module RubySync::Connectors
71
124
  # Call each_change repeatedly (or once if in once_only mode)
72
125
  # to generate events.
73
126
  # Should generally only be called by the pipeline to which it is attached.
74
- def start
127
+ def start &blk
75
128
  log.info "#{name}: Started"
76
129
  @running = true
77
130
  started()
78
131
  while @running
79
132
  each_change do |event|
133
+ if event.type == :force_resync
134
+ each_entry &blk
135
+ next
136
+ end
80
137
  if is_delete_echo?(event) || is_echo?(event)
81
138
  log.debug "Ignoring echoed event"
82
139
  else
@@ -106,12 +163,6 @@ module RubySync::Connectors
106
163
  def is_vault?
107
164
  @is_vault
108
165
  end
109
-
110
- # Returns the correct id for the given association
111
- def path_for_association(association)
112
- (is_vault?)?
113
- path_for_foreign_key(association) : path_for_own_association_key(association.key)
114
- end
115
166
 
116
167
 
117
168
  # Returns the association key for the given path. Called if this connector is
@@ -161,37 +212,77 @@ module RubySync::Connectors
161
212
  defined? associations_for
162
213
  end
163
214
 
164
- # Implement these if you want your connector to be able to act as a vault
165
215
 
166
- # def associate association, path
167
- # end
168
- #
169
- # def path_for_association association
170
- # end
171
- #
172
- #
173
- # def associations_for path
174
- # end
175
- #
176
- # def remove_association association
177
- # end
216
+ # Store association for the given path
217
+ def associate association, path
218
+ DBM.open(path_to_association_dbm_filename) do |dbm|
219
+ assocs_string = dbm[path.to_s]
220
+ assocs = (assocs_string)? Marshal.load(assocs_string) : {}
221
+ assocs[association.context] = association.key
222
+ dbm[path.to_s] = Marshal.dump(assocs)
223
+ end
224
+ DBM.open(association_to_path_dbm_filename) do |dbm|
225
+ dbm[association.to_s] = path
226
+ end
227
+ end
228
+
229
+ def path_for_association association
230
+ is_vault? or return path_for_own_association_key(association.key)
231
+ DBM.open(association_to_path_dbm_filename) do |dbm|
232
+ dbm[association.to_s]
233
+ end
234
+ end
235
+
236
+ # Default implementation does nothing
237
+ def associations_for path
238
+ DBM.open(path_to_association_dbm_filename) do |dbm|
239
+ assocs_string = dbm[path.to_s]
240
+ assocs = (assocs_string)? Marshal.load(assocs_string) : {}
241
+ assocs.values
242
+ end
243
+ end
178
244
 
245
+ # Default implementation does nothing
246
+ def remove_association association
247
+ path = nil
248
+ DBM.open(association_to_path_dbm_filename) do |dbm|
249
+ return unless path =dbm.delete(association.to_s)
250
+ end
251
+ DBM.open(path_to_association_dbm_filename) do |dbm|
252
+ assocs_string = dbm[path]
253
+ assocs = (assocs_string)? Marshal.load(assocs_string) : {}
254
+ assocs.delete(association.context) and dbm[path.to_s] = Marshal.dump(assocs)
255
+ end
256
+ end
257
+
258
+ # Could be more efficient for the default case where the
259
+ # associations are actually stored as a serialized hash but
260
+ # then it wouldn't be as generic and other implementations would
261
+ # have to reimplement it.
262
+ # def association_key_for context, path
263
+ # raise "#{name} is not a vault." unless is_vault?
264
+ # associations_for(path).each do |assoc|
265
+ # (c, key) = assoc.split(RubySync::Association.delimiter, 2)
266
+ # return key if c == context
267
+ # end
268
+ # return nil
269
+ # end
179
270
 
180
271
  def association_key_for context, path
181
- raise "#{name} is not a vault." unless is_vault?
182
- associations_for(path).each do |assoc|
183
- (c, key) = assoc.split(RubySync::Association.delimiter, 2)
184
- return key if c == context
272
+ DBM.open(path_to_association_dbm_filename) do |dbm|
273
+ assocs_string = dbm[path.to_s]
274
+ assocs = (assocs_string)? Marshal.load(assocs_string) : {}
275
+ assocs[context.to_s]
185
276
  end
186
- return nil
187
277
  end
278
+
188
279
 
189
280
  # Return the association object given the association context and path.
190
281
  # This should only be called on the vault.
191
282
  def association_for(context, path)
192
283
  raise "#{name} is not a vault." unless is_vault?
193
284
  key = association_key_for context, path
194
- RubySync::Association.new(context, key)
285
+ key and RubySync::Association.new(context, key)
195
286
  end
196
287
 
197
288
  # Should only be called on the vault. Returns the entry associated with
@@ -200,14 +291,27 @@ module RubySync::Connectors
200
291
  # association.
201
292
  def find_associated association
202
293
  path = path_for_association association
203
- self[path]
294
+ path and self[path]
204
295
  end
205
296
 
206
297
  # The context to be used to for all associations created where this
207
298
  # connector is the client.
208
299
  def association_context
209
300
  self.name
210
- end
301
+ end
302
+
303
+ def remove_mirror
304
+ File.delete_if_exists(["#{mirror_dbm_filename}.db"])
305
+ end
306
+
307
+ def remove_associations
308
+ File.delete_if_exists(["#{association_to_path_dbm_filename}.db","#{path_to_association_dbm_filename}.db"])
309
+ end
310
+
311
+ def clean
312
+ remove_associations
313
+ remove_mirror
314
+ end
211
315
 
212
316
  # Attempts to delete non-existent items may occur due to echoing. Many systems won't be able to record
213
317
  # the fact that an entry has been deleted by rubysync because after the delete, there is no entry left to
@@ -266,9 +370,8 @@ module RubySync::Connectors
266
370
  when :add
267
371
  if record[op.subject]
268
372
  existing = record[op.subject].as_array
269
- unless (existing & op.values).empty?
373
+ (existing & op.values).empty? or
270
374
  raise Exception.new("Attempt to add duplicate elements to #{name}")
271
- end
272
375
  record[op.subject] = existing + op.values
273
376
  else
274
377
  record[op.subject] = op.values
@@ -43,6 +43,7 @@ module RubySync
43
43
  call_if_exists(:target_transform, event)
44
44
  if add(event.target_path, event.payload)
45
45
  log.info "Add succeeded"
46
+ update_mirror event.target_path
46
47
  if is_vault?
47
48
  if event.association
48
49
  associate(event.association, event.target_path)
@@ -63,16 +64,33 @@ module RubySync
63
64
  path = (is_vault?)? path_for_association(event.association) : path_for_own_association_key(event.association.key)
64
65
  log.info "Deleting '#{path}' from '#{name}'"
65
66
  delete(path) or log.warn("#{name}: Attempted to delete non-existent entry '#{path}'\nMay be an echo of a delete from this connector, ignoring.")
67
+ delete_from_mirror path
66
68
  return nil # don't want to create any new associations
67
69
  end
70
+
71
+ def delete_from_mirror path
72
+ DBM.open(self.mirror_dbm_filename) do |dbm|
73
+ dbm.delete(path)
74
+ end
75
+ end
68
76
 
69
77
  def perform_modify event
70
78
  path = (is_vault?)? path_for_association(event.association) : path_for_own_association_key(event.association.key)
71
79
  raise Exception.new("#{name}: Attempted to modify non-existent entry '#{path}'") unless self[path]
72
80
  call_if_exists(:target_transform, event)
73
81
  modify path, event.payload
82
+ update_mirror path
74
83
  return (is_vault?)? nil : own_association_key_for(event.target_path)
75
84
  end
85
+
86
+ def update_mirror path
87
+ if entry = self[path]
88
+ DBM.open(self.mirror_dbm_filename) do |dbm|
89
+ dbm[path.to_s] = digest(entry)
90
+ end
91
+ end
92
+ end
93
+
76
94
  end
77
95
  end
78
96
  end
@@ -84,6 +84,9 @@ END
84
84
  nil
85
85
  end
86
86
 
87
+ def delete(path)
88
+ log.warn "Delete on CSV driver ignored for #{path}"
89
+ end
87
90
 
88
91
  end
89
92
  end
@@ -31,6 +31,7 @@ module RubySync
31
31
  log.error "in_glob not set on file connector. No files will be processed"
32
32
  return
33
33
  end
34
+ base_path # Call it before changing directory
34
35
  log.info "#{name}: Scanning #{in_path} for #{in_glob} files..."
35
36
  Dir.chdir(in_path) do |path|
36
37
  Dir.glob(in_glob) do |filename|