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.
- data/.DS_Store +0 -0
- data/.project +17 -0
- data/COPYING +339 -0
- data/HISTORY.txt +8 -0
- data/Manifest.txt +218 -0
- data/README.txt +67 -0
- data/Rakefile +31 -0
- data/bin/.DS_Store +0 -0
- data/bin/rubysync +19 -12
- data/examples/.DS_Store +0 -0
- data/examples/ar_client_webapp/log/development.log +753 -0
- data/examples/ar_client_webapp/log/production.log +0 -0
- data/examples/ar_client_webapp/log/server.log +0 -0
- data/examples/ar_client_webapp/log/test.log +0 -0
- data/examples/ar_client_webapp/public/.htaccess +40 -0
- data/examples/ar_client_webapp/tmp/sessions/ruby_sess.e2e3c63a67baef6d +0 -0
- data/examples/ar_webapp/.DS_Store +0 -0
- data/examples/ar_webapp/app/.DS_Store +0 -0
- data/examples/ar_webapp/app/views/.DS_Store +0 -0
- data/examples/ar_webapp/app/views/people/.DS_Store +0 -0
- data/examples/ar_webapp/log/development.log +5518 -0
- data/examples/ar_webapp/log/production.log +0 -0
- data/examples/ar_webapp/log/server.log +0 -0
- data/examples/ar_webapp/log/test.log +2178 -0
- data/examples/ar_webapp/public/.htaccess +40 -0
- data/examples/ar_webapp/tmp/sessions/ruby_sess.2295696b0af5f6dd +0 -0
- data/examples/ar_webapp/tmp/sessions/ruby_sess.26687aeb19c87669 +0 -0
- data/examples/ar_webapp/tmp/sessions/ruby_sess.2855a3b0c8ea840b +0 -0
- data/examples/ar_webapp/tmp/sessions/ruby_sess.45d2d48a8330ff28 +0 -0
- data/examples/ar_webapp/tmp/sessions/ruby_sess.7366b840f4ce9f12 +0 -0
- data/examples/ar_webapp/tmp/sessions/ruby_sess.b2fc3f2e6d8ae555 +0 -0
- data/examples/ar_webapp/tmp/sessions/ruby_sess.b6bf8470a62c02b0 +0 -0
- data/examples/my_ims/.DS_Store +0 -0
- data/gemspec +48 -0
- data/lib/.DS_Store +0 -0
- data/lib/net/.DS_Store +0 -0
- data/lib/ruby_sync/connectors/active_record_association_handler.rb +66 -0
- data/lib/ruby_sync/connectors/active_record_connector.rb +14 -62
- data/lib/ruby_sync/connectors/active_record_event_handler.rb +47 -0
- data/lib/ruby_sync/connectors/base_connector.rb +139 -36
- data/lib/ruby_sync/connectors/connector_event_processing.rb +18 -0
- data/lib/ruby_sync/connectors/csv_file_connector.rb +3 -0
- data/lib/ruby_sync/connectors/file_connector.rb +1 -0
- data/lib/ruby_sync/connectors/memory_connector.rb +2 -104
- data/lib/ruby_sync/connectors/xml_connector.rb +98 -0
- data/lib/ruby_sync/event.rb +7 -1
- data/lib/ruby_sync/util/utilities.rb +27 -16
- data/lib/ruby_sync.rb +10 -1
- data/lib/rubysync.rb +19 -0
- data/nbproject/private/private.properties +3 -0
- data/nbproject/project.properties +8 -0
- data/nbproject/project.xml +16 -0
- data/rubysync.tmproj +348 -0
- data/test/.DS_Store +0 -0
- data/test/ruby_sync_test.rb +8 -1
- data/test/tc_xml_connectors.rb +47 -0
- data/test/test_active_record_vault.rb +17 -14
- data/test/test_base_connector.rb +38 -0
- data/test/test_ldap_changelog.rb +97 -0
- data/test/test_ldap_connector.rb +2 -48
- data/test/test_memory_connectors.rb +8 -3
- data/test/test_rubysync.rb +28 -0
- data/test/ts_rubysync.rb +5 -3
- 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
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
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
|
-
|
86
|
-
|
87
|
-
|
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
|
191
|
-
|
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
|
-
|
145
|
+
entry[key.to_s] = value if key and value
|
195
146
|
end
|
196
|
-
|
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
|
75
|
+
def started
|
76
|
+
end
|
52
77
|
|
53
78
|
# Subclasses must override this to
|
54
|
-
# interface with the external system and generate
|
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
|
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
|
-
|
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
|
-
#
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
182
|
-
|
183
|
-
(
|
184
|
-
|
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
|
-
|
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
|
@@ -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|
|