line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Prophet::Replica; |
2
|
40
|
|
|
40
|
|
2692
|
use Any::Moose; |
|
40
|
|
|
|
|
39109
|
|
|
40
|
|
|
|
|
212
|
|
3
|
40
|
|
|
40
|
|
20430
|
use Params::Validate qw(:all); |
|
40
|
|
|
|
|
66818
|
|
|
40
|
|
|
|
|
8153
|
|
4
|
40
|
|
|
40
|
|
249
|
use File::Spec (); |
|
40
|
|
|
|
|
61
|
|
|
40
|
|
|
|
|
881
|
|
5
|
40
|
|
|
40
|
|
170
|
use File::Path qw/mkpath/; |
|
40
|
|
|
|
|
55
|
|
|
40
|
|
|
|
|
2279
|
|
6
|
|
|
|
|
|
|
|
7
|
40
|
|
|
40
|
|
192
|
use constant state_db_uuid => 'state'; |
|
40
|
|
|
|
|
53
|
|
|
40
|
|
|
|
|
3055
|
|
8
|
|
|
|
|
|
|
|
9
|
40
|
|
|
40
|
|
15420
|
use Prophet::App; |
|
40
|
|
|
|
|
177
|
|
|
40
|
|
|
|
|
176017
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
has metadata_store => ( |
12
|
|
|
|
|
|
|
is => 'rw', |
13
|
|
|
|
|
|
|
isa => 'Prophet::MetadataStore', |
14
|
|
|
|
|
|
|
documentation => 'Where metadata about other replicas is stored.', |
15
|
|
|
|
|
|
|
); |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
has resolution_db_handle => ( |
19
|
|
|
|
|
|
|
is => 'rw', |
20
|
|
|
|
|
|
|
isa => 'Prophet::Replica', |
21
|
|
|
|
|
|
|
documentation => 'Where conflict resolutions are stored.', |
22
|
|
|
|
|
|
|
); |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
has is_resdb => ( |
25
|
|
|
|
|
|
|
is => 'rw', |
26
|
|
|
|
|
|
|
isa => 'Bool', |
27
|
|
|
|
|
|
|
documentation => 'Whether this replica is a resolution db or not.' |
28
|
|
|
|
|
|
|
); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
has db_uuid => ( |
31
|
|
|
|
|
|
|
is => 'rw', |
32
|
|
|
|
|
|
|
isa => 'Str', |
33
|
|
|
|
|
|
|
documentation => 'The uuid of this replica.', |
34
|
|
|
|
|
|
|
); |
35
|
0
|
|
|
0
|
0
|
|
sub set_db_uuid { shift->db_uuid(@_) } |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
has url => ( |
38
|
|
|
|
|
|
|
is => 'rw', |
39
|
|
|
|
|
|
|
isa => 'Str', |
40
|
|
|
|
|
|
|
documentation => 'Where this replica comes from.', |
41
|
|
|
|
|
|
|
); |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
has app_handle => ( |
44
|
|
|
|
|
|
|
is => 'ro', |
45
|
|
|
|
|
|
|
isa => 'Prophet::App', |
46
|
|
|
|
|
|
|
weak_ref => 1, |
47
|
|
|
|
|
|
|
predicate => 'has_app_handle', |
48
|
|
|
|
|
|
|
); |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
has after_initialize => ( |
51
|
|
|
|
|
|
|
is => 'rw', |
52
|
|
|
|
|
|
|
isa => 'CodeRef', |
53
|
|
|
|
|
|
|
default => sub { sub {1} } # default returns a coderef |
54
|
|
|
|
|
|
|
); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
has uuid_generator => ( |
58
|
|
|
|
|
|
|
is => 'rw', |
59
|
|
|
|
|
|
|
isa => 'Prophet::UUIDGenerator', |
60
|
|
|
|
|
|
|
lazy => 1, |
61
|
|
|
|
|
|
|
default => sub { |
62
|
|
|
|
|
|
|
my $self = shift; |
63
|
|
|
|
|
|
|
my $ug = Prophet::UUIDGenerator->new( uuid_scheme => 2 ); |
64
|
|
|
|
|
|
|
return $ug; |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
); |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
our $MERGETICKET_METATYPE = '_merge_tickets'; |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=head1 NAME |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Prophet::Replica |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=head1 DESCRIPTION |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
A base class for all Prophet replicas. |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=head1 METHODS |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=head3 get_handle |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
Determines what replica class to use and instantiates it. Returns the |
86
|
|
|
|
|
|
|
new replica object. |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=cut |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub get_handle { |
91
|
0
|
|
|
0
|
1
|
|
my $class = shift; |
92
|
0
|
0
|
|
|
|
|
my %args = @_ == 1 ? %{ $_[0] } : @_; |
|
0
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
|
94
|
0
|
|
|
|
|
|
my ( $new_class, $scheme, $url ) = $class->_url_to_replica_class(%args); |
95
|
|
|
|
|
|
|
|
96
|
0
|
0
|
|
|
|
|
if ( !$new_class ) { |
97
|
0
|
|
|
|
|
|
$class->log_fatal( |
98
|
0
|
|
|
|
|
|
"I don't know how to handle the replica URL you provided - '@{[ $args{url}]}'." |
99
|
|
|
|
|
|
|
."\nIs your syntax correct?" |
100
|
|
|
|
|
|
|
); |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
|
103
|
0
|
|
|
|
|
|
Prophet::App->require($new_class); |
104
|
0
|
|
|
|
|
|
my $handle = $new_class->new(%args); |
105
|
|
|
|
|
|
|
|
106
|
0
|
0
|
0
|
|
|
|
if ($handle->replica_exists && $handle->db_uuid) { |
107
|
0
|
|
|
|
|
|
$handle->uuid_generator->set_uuid_scheme($handle->db_uuid); |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
|
110
|
0
|
|
|
|
|
|
return $handle; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub initialize { |
116
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
117
|
0
|
|
|
|
|
|
my %args = validate( |
118
|
|
|
|
|
|
|
@_, |
119
|
|
|
|
|
|
|
{ db_uuid => 0, |
120
|
|
|
|
|
|
|
replica_uuid => 0, |
121
|
|
|
|
|
|
|
resdb_uuid => 0, |
122
|
|
|
|
|
|
|
resdb_replica_uuid => 0, |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
); |
125
|
|
|
|
|
|
|
|
126
|
0
|
0
|
|
|
|
|
if ( !$self->fs_root_parent ) { |
127
|
|
|
|
|
|
|
|
128
|
0
|
0
|
|
|
|
|
if ( $self->can_write_changesets ) { |
129
|
0
|
|
|
|
|
|
die "We can only create local prophet replicas. It looks like you're trying to create " . $self->url; |
130
|
|
|
|
|
|
|
} else { |
131
|
0
|
|
|
|
|
|
die "Prophet couldn't find a replica at \"" |
132
|
|
|
|
|
|
|
. $self->url |
133
|
|
|
|
|
|
|
. "\"\n\n" |
134
|
|
|
|
|
|
|
. "Please check the URL and try again.\n"; |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
0
|
0
|
|
|
|
|
return undef if $self->replica_exists; |
140
|
|
|
|
|
|
|
|
141
|
0
|
0
|
|
|
|
|
$self->uuid_generator->set_uuid_scheme($args{'db_uuid'}) if ($args{db_uuid}); |
142
|
|
|
|
|
|
|
|
143
|
0
|
|
|
|
|
|
for ( $self->_on_initialize_create_paths ) { |
144
|
0
|
|
|
|
|
|
mkpath( [ File::Spec->catdir( $self->fs_root => $_ ) ] ); |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
0
|
|
|
|
|
|
$self->initialize_backend(%args); |
148
|
0
|
|
|
|
|
|
$self->after_initialize->($self); |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
=head2 store_local_metadata KEY => VALUE |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Takes a key and a value. |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
Store some bit of metadata in a durable local datastore. Metadata isn't propagated |
158
|
|
|
|
|
|
|
when replicas are synced. |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Returns true or false. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=cut |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=head2 fetch_local_metadata KEY |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
Takes a scalar key. |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
Fetches a bit of metadata from the local metadata store. |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
Returns the value of the key found in the local metadata store. |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
Returns undef if there's no value for the key in the local metadata store. |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
=cut |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
sub replica_exists { |
177
|
0
|
|
|
0
|
0
|
|
return 1; # XXX TODO HACK |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
sub can_initialize { |
181
|
0
|
|
|
0
|
0
|
|
return undef; |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head3 _url_to_replica_class |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
Returns the replica class for the given url based on its scheme. |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=cut |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
sub _url_to_replica_class { |
191
|
0
|
|
|
0
|
|
|
my $self = shift; |
192
|
0
|
|
|
|
|
|
my %args = (@_); |
193
|
0
|
|
|
|
|
|
my $url = $args{'url'}; |
194
|
0
|
|
|
|
|
|
my ( $scheme, $real_url ) = $url =~ /^([^:]*?):(.*)$/; |
195
|
|
|
|
|
|
|
|
196
|
0
|
0
|
|
|
|
|
return undef unless $scheme; |
197
|
|
|
|
|
|
|
|
198
|
0
|
|
|
|
|
|
for my $class ( |
199
|
|
|
|
|
|
|
ref( $args{app_handle} ) . "::Replica::" . $scheme, |
200
|
|
|
|
|
|
|
"Prophet::Replica::".$scheme ) { |
201
|
0
|
0
|
|
|
|
|
Prophet::App->try_to_require($class) || next; |
202
|
0
|
|
|
|
|
|
return ( $class, $scheme, $real_url ); |
203
|
|
|
|
|
|
|
} |
204
|
0
|
|
|
|
|
|
return undef; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=head3 import_changesets { from => L ... } |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
Given a L to import changes from, traverse all the |
210
|
|
|
|
|
|
|
changesets we haven't seen before and integrate them into this replica. |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
This routine calls L on the 'from' replica, |
213
|
|
|
|
|
|
|
passing in the most recent changeset the current replica has seen |
214
|
|
|
|
|
|
|
and a callback routine which calls L on the |
215
|
|
|
|
|
|
|
local replica. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
That callback itself takes a callback, L |
218
|
|
|
|
|
|
|
, which a replica implementation can use to perform some action |
219
|
|
|
|
|
|
|
after a changeset is integrated into a peer. L |
220
|
|
|
|
|
|
|
takes a paramhash, currently with only a single key, 'changeset'. |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=cut |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub import_changesets { |
225
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
226
|
0
|
|
|
|
|
|
my %args = validate( |
227
|
|
|
|
|
|
|
@_, |
228
|
|
|
|
|
|
|
{ from => { isa => 'Prophet::Replica' }, |
229
|
|
|
|
|
|
|
resdb => { optional => 1 }, |
230
|
|
|
|
|
|
|
resolver => { optional => 1 }, |
231
|
|
|
|
|
|
|
resolver_class => { optional => 1 }, |
232
|
|
|
|
|
|
|
conflict_callback => { type => CODEREF, optional => 1 }, |
233
|
|
|
|
|
|
|
reporting_callback => { type => CODEREF, optional => 1 }, |
234
|
|
|
|
|
|
|
force => { optional => 1 }, |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
); |
237
|
|
|
|
|
|
|
|
238
|
0
|
|
|
|
|
|
my $source = $args{'from'}; |
239
|
|
|
|
|
|
|
|
240
|
0
|
|
|
|
|
|
$self->_check_db_uuids_on_merge(for => $source, force => $args{'force'}); |
241
|
|
|
|
|
|
|
|
242
|
0
|
0
|
|
|
|
|
warn "The source (@{[$source->url]}) does not exist" unless ($source->replica_exists); |
|
0
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
|
244
|
0
|
|
|
|
|
|
$self->log_debug("Integrating changesets from ".$source->uuid. " after ". $self->last_changeset_from_source( $self->uuid )); |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
$source->traverse_changesets( |
247
|
|
|
|
|
|
|
after => $self->last_changeset_from_source( $source->uuid ), |
248
|
|
|
|
|
|
|
before_load_changeset_callback => sub { |
249
|
0
|
|
|
0
|
|
|
my %args = (@_); |
250
|
0
|
|
|
|
|
|
my ($seq, $orig_uuid, $orig_seq, $key) = @{$args{changeset_metadata}}; |
|
0
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# skip changesets we've seen before |
252
|
0
|
0
|
|
|
|
|
if ( $self->has_seen_changeset( source_uuid => $orig_uuid, sequence_no => $orig_seq) ){ |
253
|
|
|
|
|
|
|
# If we've seen the changeset, yet we still got here, it means we saw it by original |
254
|
|
|
|
|
|
|
# replica/sequence pair, but not # the direct upstream's uuid/sequence pair. |
255
|
|
|
|
|
|
|
# recording that can help performance a whole bunch for next sync |
256
|
0
|
0
|
0
|
|
|
|
if ($source->uuid && $seq > $self->last_changeset_from_source($source->uuid)) { |
257
|
0
|
|
|
|
|
|
$self->record_last_changeset_from_replica( $source->uuid => $seq); |
258
|
|
|
|
|
|
|
} |
259
|
0
|
|
|
|
|
|
return undef; |
260
|
|
|
|
|
|
|
} else { |
261
|
0
|
|
|
|
|
|
return 1; |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
}, |
265
|
|
|
|
|
|
|
callback => sub { |
266
|
0
|
|
|
0
|
|
|
my %callback_args = (@_); |
267
|
|
|
|
|
|
|
$self->integrate_changeset( |
268
|
|
|
|
|
|
|
changeset => $callback_args{changeset}, |
269
|
|
|
|
|
|
|
conflict_callback => $args{'conflict_callback'}, |
270
|
|
|
|
|
|
|
reporting_callback => $args{'reporting_callback'}, |
271
|
|
|
|
|
|
|
resolver => $args{'resolver'}, |
272
|
|
|
|
|
|
|
resolver_class => $args{'resolver_class'}, |
273
|
0
|
|
|
|
|
|
resdb => $args{'resdb'}, |
274
|
|
|
|
|
|
|
); |
275
|
|
|
|
|
|
|
|
276
|
0
|
0
|
|
|
|
|
if ( ref( $callback_args{'after_integrate_changeset'} ) ) { |
277
|
0
|
|
|
|
|
|
$callback_args{'after_integrate_changeset'}->( changeset => $callback_args{'changeset'} ); |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
} |
281
|
0
|
|
|
|
|
|
); |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
=head3 import_resolutions_from_remote_replica { from => L ... } |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
Takes a L object (and possibly some optional arguments) |
287
|
|
|
|
|
|
|
and imports its resolution changesets into this replica's resolution |
288
|
|
|
|
|
|
|
database. |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
Returns immediately if either the source replica or the target replica lack |
291
|
|
|
|
|
|
|
a resolution database. |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=cut |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
sub import_resolutions_from_remote_replica { |
296
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
297
|
0
|
|
|
|
|
|
my %args = validate( |
298
|
|
|
|
|
|
|
@_, |
299
|
|
|
|
|
|
|
{ from => { isa => 'Prophet::Replica' }, |
300
|
|
|
|
|
|
|
resolver => { optional => 1 }, |
301
|
|
|
|
|
|
|
resolver_class => { optional => 1 }, |
302
|
|
|
|
|
|
|
conflict_callback => { optional => 1 }, |
303
|
|
|
|
|
|
|
force => { optional => 1 }, |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
); |
306
|
0
|
|
|
|
|
|
my $source = $args{'from'}; |
307
|
|
|
|
|
|
|
|
308
|
0
|
0
|
|
|
|
|
return unless $self->resolution_db_handle; |
309
|
0
|
0
|
|
|
|
|
return unless $source->resolution_db_handle; |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
$self->resolution_db_handle->import_changesets( |
312
|
|
|
|
|
|
|
from => $source->resolution_db_handle, |
313
|
0
|
|
|
0
|
|
|
resolver => sub { die "not implemented yet" }, |
314
|
|
|
|
|
|
|
force => $args{force}, |
315
|
0
|
|
|
|
|
|
); |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=head3 integrate_changeset L |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
Given a L, integrate each and every change within that |
321
|
|
|
|
|
|
|
changeset into the handle's replica. |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
If there are conflicts, generate a nullification change, figure out a conflict |
324
|
|
|
|
|
|
|
resolution and apply the nullification, original change and resolution all at |
325
|
|
|
|
|
|
|
once (as three separate changes). |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
If there are no conflicts, just apply the change. |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
This routine also records that we've seen this changeset (and hence everything |
330
|
|
|
|
|
|
|
before it) from both the peer who sent it to us AND the replica which originally |
331
|
|
|
|
|
|
|
created it. |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
=cut |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
sub integrate_changeset { |
336
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
337
|
0
|
|
|
|
|
|
my %args = validate( |
338
|
|
|
|
|
|
|
@_, |
339
|
|
|
|
|
|
|
{ changeset => { isa => 'Prophet::ChangeSet' }, |
340
|
|
|
|
|
|
|
resolver => { optional => 1 }, |
341
|
|
|
|
|
|
|
resolver_class => { optional => 1 }, |
342
|
|
|
|
|
|
|
resdb => { optional => 1 }, |
343
|
|
|
|
|
|
|
conflict_callback => { optional => 1 }, |
344
|
|
|
|
|
|
|
reporting_callback => { optional => 1 } |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
); |
347
|
|
|
|
|
|
|
|
348
|
0
|
|
|
|
|
|
my $changeset = $args{'changeset'}; |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
|
351
|
0
|
|
|
|
|
|
$self->log_debug("Considering changeset ".$changeset->original_sequence_no . |
352
|
|
|
|
|
|
|
" from " . $self->display_name_for_replica($changeset->original_source_uuid)); |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
# when we start to integrate a changeset, we need to do a bit of housekeeping |
355
|
|
|
|
|
|
|
# We never want to merge in: |
356
|
|
|
|
|
|
|
# - merge tickets that describe merges from the local record |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
# When we integrate changes, sometimes we will get handed changes we |
359
|
|
|
|
|
|
|
# already know about. |
360
|
|
|
|
|
|
|
# - changes from local |
361
|
|
|
|
|
|
|
# - changes from some other party we've merged from |
362
|
|
|
|
|
|
|
# - merge tickets for the same |
363
|
|
|
|
|
|
|
# we'll want to skip or remove those changesets |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
|
366
|
0
|
0
|
|
|
|
|
if (! $self->should_accept_changeset($changeset) ){ |
|
|
0
|
|
|
|
|
|
367
|
|
|
|
|
|
|
# if it's a changeset we don't care about, mark it as seen and move on |
368
|
0
|
|
|
|
|
|
$self->record_integration_of_changeset($changeset); |
369
|
|
|
|
|
|
|
$args{'reporting_callback'}->( changeset => $changeset, ) |
370
|
0
|
0
|
|
|
|
|
if ( $args{'reporting_callback'} ); |
371
|
0
|
|
|
|
|
|
return; |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
elsif ( my $conflict = $self->conflicts_from_changeset($changeset) ) { |
374
|
0
|
|
|
|
|
|
$self->log_debug( "Integrating conflicting changeset " |
375
|
|
|
|
|
|
|
. $changeset->original_sequence_no |
376
|
|
|
|
|
|
|
. " from " |
377
|
|
|
|
|
|
|
. $self->display_name_for_replica( $changeset->original_source_uuid ) ); |
378
|
0
|
0
|
|
|
|
|
$args{conflict_callback}->($conflict) if $args{'conflict_callback'}; |
379
|
0
|
0
|
|
0
|
|
|
$conflict->resolvers( [ sub { $args{resolver}->(@_) } ] ) if $args{resolver}; |
|
0
|
|
|
|
|
|
|
380
|
0
|
0
|
|
|
|
|
if ( $args{resolver_class} ) { |
381
|
0
|
0
|
|
|
|
|
Prophet::App->require( $args{resolver_class} ) || die $@; |
382
|
|
|
|
|
|
|
$conflict->resolvers( |
383
|
|
|
|
|
|
|
[ sub { |
384
|
0
|
|
|
0
|
|
|
$args{resolver_class}->new->run(@_); |
385
|
|
|
|
|
|
|
} |
386
|
0
|
|
|
|
|
|
] |
387
|
|
|
|
|
|
|
); |
388
|
|
|
|
|
|
|
} |
389
|
0
|
|
|
|
|
|
my $resolutions = $conflict->generate_resolution( $args{resdb} ); |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
#figure out our conflict resolution |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# IMPORTANT: these should be an atomic unit. dying here would be poor. |
394
|
|
|
|
|
|
|
# BUT WE WANT THEM AS THREE DIFFERENT CHANGESETS |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
# integrate the nullification change |
397
|
0
|
|
|
|
|
|
$self->record_changes( $conflict->nullification_changeset ); |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
# integrate the original change |
400
|
0
|
|
|
|
|
|
$self->record_changeset_and_integration($changeset); |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# integrate the conflict resolution change |
403
|
0
|
|
|
|
|
|
$self->record_resolutions( $conflict->resolution_changeset ); |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
$args{'reporting_callback'}->( |
406
|
|
|
|
|
|
|
changeset => $changeset, |
407
|
|
|
|
|
|
|
conflict => $conflict |
408
|
0
|
0
|
|
|
|
|
) if ( $args{'reporting_callback'} ); |
409
|
0
|
|
|
|
|
|
return 1; |
410
|
|
|
|
|
|
|
} else { |
411
|
0
|
|
|
|
|
|
$self->log_debug("Integrating changeset ".$changeset->original_sequence_no . |
412
|
|
|
|
|
|
|
" from " . $self->display_name_for_replica($changeset->original_source_uuid)); |
413
|
0
|
|
|
|
|
|
$self->record_changeset_and_integration($changeset); |
414
|
0
|
0
|
|
|
|
|
$args{'reporting_callback'}->( changeset => $changeset ) if ( $args{'reporting_callback'} ); |
415
|
0
|
|
|
|
|
|
return 1; |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
} |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
=head3 record_changeset_and_integration L |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
Given a L, integrate each and every change within that |
422
|
|
|
|
|
|
|
changeset into the handle's replica. |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
If the state handle is in the middle of an edit, the integration of this |
425
|
|
|
|
|
|
|
changeset is recorded as part of that edit; if not, it is recorded as a new |
426
|
|
|
|
|
|
|
edit. |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
=cut |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
sub record_changeset_and_integration { |
431
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
432
|
0
|
|
|
|
|
|
my $changeset = shift; |
433
|
|
|
|
|
|
|
|
434
|
0
|
|
|
|
|
|
$self->begin_edit(source => $changeset); |
435
|
0
|
|
|
|
|
|
$self->record_changes($changeset); |
436
|
|
|
|
|
|
|
|
437
|
0
|
|
|
|
|
|
$self->record_integration_of_changeset($changeset); |
438
|
|
|
|
|
|
|
|
439
|
0
|
|
|
|
|
|
$self->_set_original_source_metadata_for_current_edit($changeset); |
440
|
0
|
|
|
|
|
|
$self->commit_edit; |
441
|
|
|
|
|
|
|
|
442
|
0
|
|
|
|
|
|
return; |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=head3 last_changeset_from_source $SOURCE_UUID |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
Returns the last changeset id seen from the replica identified by $SOURCE_UUID. |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=cut |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
sub last_changeset_from_source { |
452
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
453
|
0
|
|
|
|
|
|
my ($source) = validate_pos( @_, { type => SCALAR } ); |
454
|
|
|
|
|
|
|
|
455
|
0
|
|
|
|
|
|
my $changeset_num = $self->fetch_local_metadata('last-changeset-from-'.$source); |
456
|
|
|
|
|
|
|
# 0 is a valid changeset # |
457
|
0
|
0
|
|
|
|
|
return defined $changeset_num ? $changeset_num : -1; |
458
|
|
|
|
|
|
|
} |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=head3 has_seen_changeset { source_uuid => , sequence_no => } |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Returns true if we've previously integrated this changeset, even if we |
464
|
|
|
|
|
|
|
originally received it from a different peer. |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=cut |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
sub has_seen_changeset { |
469
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
470
|
0
|
|
|
|
|
|
my %args = validate( @_, {source_uuid => 1, sequence_no => 1}); |
471
|
|
|
|
|
|
|
$self->log_debug("Checking to see if we've ever seen changeset " . |
472
|
|
|
|
|
|
|
$args{sequence_no} . " from " . |
473
|
0
|
|
|
|
|
|
$self->display_name_for_replica($args{source_uuid})); |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
# If the changeset originated locally, we never want it |
476
|
0
|
0
|
|
|
|
|
if ($args{source_uuid} eq $self->uuid ) { |
|
|
0
|
|
|
|
|
|
477
|
0
|
|
|
|
|
|
$self->log_debug("\t - We have. (It originated locally.)"); |
478
|
0
|
|
|
|
|
|
return 1 |
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
# Otherwise, if the we have a merge ticket from the source, we don't want |
481
|
|
|
|
|
|
|
# the changeset if the source's sequence # is >= the changeset's sequence |
482
|
|
|
|
|
|
|
# #, we can safely skip it |
483
|
|
|
|
|
|
|
elsif ( $self->last_changeset_from_source( $args{source_uuid} ) >= $args{sequence_no} ) { |
484
|
0
|
|
|
|
|
|
$self->log_debug("\t - We have seen this or a more recent changeset from remote."); |
485
|
0
|
|
|
|
|
|
return 1; |
486
|
|
|
|
|
|
|
} else { |
487
|
0
|
|
|
|
|
|
return undef; |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
=head3 changeset_will_conflict L |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
Returns true if any change that's part of this changeset won't apply cleanly to |
494
|
|
|
|
|
|
|
the head of the current replica. |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=cut |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
sub changeset_will_conflict { |
499
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
500
|
0
|
|
|
|
|
|
my ($changeset) = validate_pos( @_, { isa => "Prophet::ChangeSet" } ); |
501
|
|
|
|
|
|
|
|
502
|
0
|
0
|
|
|
|
|
return 1 if ( $self->conflicts_from_changeset($changeset) ); |
503
|
|
|
|
|
|
|
|
504
|
0
|
|
|
|
|
|
return undef; |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=head3 conflicts_from_changeset L |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
Returns a L object if the supplied L |
510
|
|
|
|
|
|
|
will generate conflicts if applied to the current replica. |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
Returns undef if the current changeset wouldn't generate a conflict. |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
=cut |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
sub conflicts_from_changeset { |
517
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
518
|
0
|
|
|
|
|
|
my ($changeset) = validate_pos( @_, { isa => "Prophet::ChangeSet" } ); |
519
|
0
|
|
|
|
|
|
require Prophet::Conflict; |
520
|
0
|
|
|
|
|
|
my $conflict = Prophet::Conflict->new( { changeset => $changeset, |
521
|
|
|
|
|
|
|
prophet_handle => $self} ); |
522
|
|
|
|
|
|
|
|
523
|
0
|
|
|
|
|
|
$conflict->analyze_changeset(); |
524
|
|
|
|
|
|
|
|
525
|
0
|
0
|
|
|
|
|
return undef unless $conflict->has_conflicting_changes; |
526
|
|
|
|
|
|
|
|
527
|
0
|
|
|
|
|
|
$self->log_debug("Conflicting changeset: ".JSON::to_json($conflict, {allow_blessed => 1})); |
528
|
|
|
|
|
|
|
|
529
|
0
|
|
|
|
|
|
return $conflict; |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
sub _check_db_uuids_on_merge { |
533
|
0
|
|
|
0
|
|
|
my $self = shift; |
534
|
0
|
|
|
|
|
|
my %args = validate( @_, |
535
|
|
|
|
|
|
|
{ for => { isa => 'Prophet::Replica' }, |
536
|
|
|
|
|
|
|
force => 0, |
537
|
|
|
|
|
|
|
}); |
538
|
0
|
0
|
0
|
|
|
|
if ( $self->db_uuid && $args{for}->db_uuid |
|
|
|
0
|
|
|
|
|
539
|
|
|
|
|
|
|
&& $self->db_uuid ne $args{for}->db_uuid ) { |
540
|
0
|
0
|
|
|
|
|
unless ( $args{'force'} ) { |
541
|
|
|
|
|
|
|
die "You are trying to merge two different databases! This is NOT\n" |
542
|
|
|
|
|
|
|
. "recommended. If you really want to do this, add '--force' to\n" |
543
|
|
|
|
|
|
|
. "your commandline.\n\n" |
544
|
|
|
|
|
|
|
. "Local database: " |
545
|
|
|
|
|
|
|
. $self->db_uuid . "\n" |
546
|
|
|
|
|
|
|
. "Remote database: " |
547
|
0
|
|
|
|
|
|
. $args{for}->db_uuid . "\n"; |
548
|
|
|
|
|
|
|
} |
549
|
|
|
|
|
|
|
} |
550
|
|
|
|
|
|
|
} |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
=head3 should_accept_changeset { from => L, changeset => L } |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
Returns true if this replica hasn't yet seen the changeset C. |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
=cut |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
sub should_accept_changeset { |
559
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
560
|
0
|
|
|
|
|
|
my ($changeset) = validate_pos( @_, { changeset => { isa => 'Prophet::ChangeSet' } }); |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
|
563
|
0
|
|
|
|
|
|
$self->log_debug("Should I accept " .$changeset->original_sequence_no . |
564
|
|
|
|
|
|
|
" from ".$self->display_name_for_replica($changeset->original_source_uuid)); |
565
|
0
|
0
|
|
|
|
|
return undef if (! $changeset->has_changes); |
566
|
0
|
0
|
0
|
|
|
|
return undef if ( $changeset->is_nullification || $changeset->is_resolution ); |
567
|
0
|
0
|
|
|
|
|
return undef if $self->has_seen_changeset( sequence_no => $changeset->original_sequence_no, source_uuid => $changeset->original_source_uuid ); |
568
|
0
|
|
|
|
|
|
$self->log_debug("Yes, it has changes, isn't a nullification and I haven't seen it before"); |
569
|
|
|
|
|
|
|
|
570
|
0
|
|
|
|
|
|
return 1; |
571
|
|
|
|
|
|
|
} |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
=head3 fetch_changesets { after => SEQUENCE_NO } |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
Fetch all changesets from this replica after the local sequence number SEQUENCE_NO. |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
Returns a reference to an array of L objects. |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
See also L for replica implementations to provide |
580
|
|
|
|
|
|
|
streamly interface. |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
=cut |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
sub fetch_changesets { |
585
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
586
|
0
|
|
|
|
|
|
my %args = validate( @_, { after => 1 } ); |
587
|
0
|
|
|
|
|
|
my @results; |
588
|
|
|
|
|
|
|
|
589
|
0
|
|
|
0
|
|
|
$self->traverse_changesets( %args, callback => sub { my %args = @_; push @results, $args{changeset} } ); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
|
591
|
0
|
|
|
|
|
|
return \@results; |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
=head2 methods to be implemented by a replica backend |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
=head3 uuid |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
Returns this replica's uuid. |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
=cut |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
0
|
1
|
|
sub uuid {} |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
=head3 latest_sequence_no |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
Returns the sequence # of the most recently committed changeset. |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
=cut |
609
|
|
|
|
|
|
|
|
610
|
0
|
|
|
0
|
1
|
|
sub latest_sequence_no { return undef } |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
=head3 find_or_create_luid { uuid => UUID } |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
Finds or creates a LUID for the given UUID. |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
=cut |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
sub find_or_create_luid { |
619
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
620
|
0
|
|
|
|
|
|
my %args = validate( @_, { uuid => 1 } ); |
621
|
|
|
|
|
|
|
|
622
|
0
|
|
|
|
|
|
my $mapping = $self->_read_guid2luid_mappings; |
623
|
|
|
|
|
|
|
|
624
|
0
|
0
|
|
|
|
|
if (!exists($mapping->{ $args{'uuid'} })) { |
625
|
0
|
|
|
|
|
|
$mapping->{ $args{'uuid'} } = $self->_create_luid($mapping); |
626
|
0
|
|
|
|
|
|
$self->_write_guid2luid_mappings($mapping); |
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
|
629
|
0
|
|
|
|
|
|
return $mapping->{ $args{'uuid'} }; |
630
|
|
|
|
|
|
|
} |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
sub find_luid_by_uuid { |
633
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
634
|
0
|
|
|
|
|
|
my %args = validate( @_, { uuid => 1 } ); |
635
|
0
|
|
|
|
|
|
my $mapping = $self->_read_guid2luid_mappings; |
636
|
|
|
|
|
|
|
|
637
|
0
|
0
|
|
|
|
|
if (!exists($mapping->{ $args{'uuid'} })) { |
638
|
0
|
|
|
|
|
|
return undef; |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
|
641
|
0
|
|
|
|
|
|
return $mapping->{ $args{'uuid'} }; |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=head3 find_uuid_by_luid { luid => LUID } |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
Finds the UUID for the given LUID. Returns C if the LUID is not known. |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
=cut |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
sub find_uuid_by_luid { |
653
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
654
|
0
|
|
|
|
|
|
my %args = validate( @_, { luid => 1 } ); |
655
|
|
|
|
|
|
|
|
656
|
0
|
|
|
|
|
|
my $mapping = $self->_read_luid2guid_mappings; |
657
|
0
|
|
|
|
|
|
return $mapping->{ $args{'luid'} }; |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
=head3 _create_luid ( 'uuid' => 'luid' ) |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
Given a UUID => LUID hash mapping, return a new unused LUID (one |
663
|
|
|
|
|
|
|
higher than the mapping's current highest luid). |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
=cut |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
sub _create_luid { |
668
|
0
|
|
|
0
|
|
|
my $self = shift; |
669
|
0
|
|
|
|
|
|
my $map = shift; |
670
|
|
|
|
|
|
|
|
671
|
0
|
|
|
|
|
|
return ++$map->{'_meta'}{'maximum_luid'}; |
672
|
|
|
|
|
|
|
} |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
=head3 _do_userdata_read $PATH $DEFAULT |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
Returns a reference to the parsed JSON contents of the file |
677
|
|
|
|
|
|
|
given by C<$PATH> in the replica's userdata directory. |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
Returns C<$DEFAULT> if the file does not exist. |
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
=cut |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
sub _do_userdata_read { |
684
|
0
|
|
|
0
|
|
|
my $self = shift; |
685
|
0
|
|
|
|
|
|
my $path = shift; |
686
|
0
|
|
|
|
|
|
my $default = shift; |
687
|
0
|
|
0
|
|
|
|
my $json = $self->read_userdata( path => $path ) || $default; |
688
|
0
|
|
|
|
|
|
require JSON; |
689
|
0
|
|
|
|
|
|
return JSON::from_json($json, { utf8 => 1 }); |
690
|
|
|
|
|
|
|
} |
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
=head3 _do_userdata_write $PATH $VALUE |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
serializes C<$VALUE> to JSON and writes it to the file given by C<$PATH> |
695
|
|
|
|
|
|
|
in the replica's userdata directory, creating parent directories as |
696
|
|
|
|
|
|
|
necessary. |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
=cut |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
sub _do_userdata_write { |
701
|
0
|
|
|
0
|
|
|
my $self = shift; |
702
|
0
|
|
|
|
|
|
my $path = shift; |
703
|
0
|
|
|
|
|
|
my $value = shift; |
704
|
|
|
|
|
|
|
|
705
|
0
|
|
|
|
|
|
require JSON; |
706
|
0
|
|
|
|
|
|
my $content = JSON::to_json($value, { canonical => 1, pretty => 0, utf8 => 1 }); |
707
|
|
|
|
|
|
|
|
708
|
0
|
|
|
|
|
|
$self->write_userdata( |
709
|
|
|
|
|
|
|
path => $path, |
710
|
|
|
|
|
|
|
content => $content, |
711
|
|
|
|
|
|
|
); |
712
|
|
|
|
|
|
|
} |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
=head3 _upstream_replica_cache_file |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
A string representing the name of the file where replica URLs that have been |
717
|
|
|
|
|
|
|
previously pulled from are cached. |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
=cut |
720
|
|
|
|
|
|
|
|
721
|
0
|
|
|
0
|
|
|
sub _upstream_replica_cache_file { "upstream-replica-cache" } |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
=head3 _read_cached_upstream_replicas |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
Returns a list of cached upstream replica URLs, or an empty list if |
726
|
|
|
|
|
|
|
there are no cached URLs. |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
=cut |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
sub _read_cached_upstream_replicas { |
731
|
0
|
|
|
0
|
|
|
my $self = shift; |
732
|
0
|
0
|
|
|
|
|
return @{ $self->_do_userdata_read( $self->_upstream_replica_cache_file, '[]' ) || [] }; |
|
0
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
} |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
=head3 _write_cached_upstream_replicas @REPLICAS |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
writes the replica URLs given by C<@REPLICAS> to the upstream replica |
738
|
|
|
|
|
|
|
cache file. |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
=cut |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
sub _write_cached_upstream_replicas { |
743
|
0
|
|
|
0
|
|
|
my $self = shift; |
744
|
0
|
|
|
|
|
|
my @replicas = @_; |
745
|
0
|
|
|
|
|
|
return $self->_do_userdata_write( $self->_upstream_replica_cache_file, [@replicas] ); |
746
|
|
|
|
|
|
|
} |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
=head3 _guid2luid_file |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
The file in the replica's userdata directory which contains a serialized |
751
|
|
|
|
|
|
|
JSON UUID => LUID hash mapping. |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
=cut |
754
|
|
|
|
|
|
|
|
755
|
0
|
|
|
0
|
|
|
sub _guid2luid_file { "local-id-cache" } |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
=head3 _read_guid2luid_mappings |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
Returns a UUID => LUID hashref for this replica. |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
=cut |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
sub _read_guid2luid_mappings { |
764
|
0
|
|
|
0
|
|
|
my $self = shift; |
765
|
0
|
|
|
|
|
|
return $self->_do_userdata_read( $self->_guid2luid_file, '{}' ); |
766
|
|
|
|
|
|
|
} |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
=head3 _write_guid2luid_mappings ( 'uuid' => 'luid' ) |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
Writes the given UUID => LUID hash map to C as serialized |
771
|
|
|
|
|
|
|
JSON. |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
=cut |
774
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
sub _write_guid2luid_mappings { |
776
|
0
|
|
|
0
|
|
|
my $self = shift; |
777
|
0
|
|
|
|
|
|
my $map = shift; |
778
|
|
|
|
|
|
|
|
779
|
0
|
|
|
|
|
|
return $self->_do_userdata_write( $self->_guid2luid_file, $map ); |
780
|
|
|
|
|
|
|
} |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
=head3 _read_luid2guid_mappings |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
Returns a LUID => UUID hashref for this replica. |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
=cut |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
sub _read_luid2guid_mappings { |
789
|
0
|
|
|
0
|
|
|
my $self = shift; |
790
|
0
|
|
|
|
|
|
my $guid2luid = $self->_read_guid2luid_mappings(@_); |
791
|
0
|
|
|
|
|
|
delete $guid2luid->{'_meta'}; |
792
|
0
|
|
|
|
|
|
my %luid2guid = reverse %$guid2luid; |
793
|
0
|
|
|
|
|
|
return \%luid2guid; |
794
|
|
|
|
|
|
|
} |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
=head3 traverse_changesets { after => SEQUENCE_NO, until => SEQUENCE_NO, callback => sub { my %data = (changeset => undef, @_} } |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
Walk through each changeset in the replica after SEQUENCE_NO, calling the |
799
|
|
|
|
|
|
|
C for each one in turn. |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
=cut |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
sub traverse_changesets { |
804
|
0
|
|
|
0
|
1
|
|
my $class = blessed($_[0]); |
805
|
0
|
|
|
|
|
|
Carp::confess "$class has failed to implement a 'traverse_changesets' method for their replica type."; |
806
|
|
|
|
|
|
|
} |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
=head3 can_read_changesets |
809
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
Returns true if this source is one we know how to read from (and have |
811
|
|
|
|
|
|
|
permission to do so). |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
=cut |
814
|
|
|
|
|
|
|
|
815
|
0
|
|
|
0
|
1
|
|
sub can_read_changesets { undef } |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
=head3 can_write_changesets |
818
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
Returns true if this source is one we know how to write to (and have permission |
820
|
|
|
|
|
|
|
to write to). |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
Returns false otherwise. |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
=cut |
825
|
|
|
|
|
|
|
|
826
|
0
|
|
|
0
|
1
|
|
sub can_write_changesets { undef } |
827
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
=head3 record_resolutions L |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
Given a resolution changeset, record all the resolution changesets as well as |
831
|
|
|
|
|
|
|
resolution records in the local resolution database. |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
Called ONLY on local resolution creation. (Synced resolutions are just synced |
834
|
|
|
|
|
|
|
as records.) |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
=cut |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
sub record_resolutions { |
839
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
840
|
0
|
|
|
|
|
|
my ($changeset) = validate_pos(@_, { isa => 'Prophet::ChangeSet'}); |
841
|
|
|
|
|
|
|
|
842
|
0
|
0
|
|
|
|
|
$self->_unimplemented("record_resolutions (since there is no writable handle)") |
843
|
|
|
|
|
|
|
unless ($self->can_write_changesets); |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
# If we have a resolution db handle, record the resolutions there. |
846
|
|
|
|
|
|
|
# Otherwise, record them locally |
847
|
0
|
|
0
|
|
|
|
my $res_handle = $self->resolution_db_handle || $self; |
848
|
|
|
|
|
|
|
|
849
|
0
|
0
|
|
|
|
|
return unless $changeset->has_changes; |
850
|
|
|
|
|
|
|
|
851
|
0
|
|
|
|
|
|
$self->begin_edit(source => $changeset); |
852
|
0
|
|
|
|
|
|
$self->record_changes($changeset); |
853
|
0
|
|
|
|
|
|
$res_handle->_record_resolution($_) for $changeset->changes; |
854
|
0
|
|
|
|
|
|
$self->commit_edit(); |
855
|
|
|
|
|
|
|
} |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
=head3 _record_resolution L |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
Called ONLY on local resolution creation. (Synced resolutions are just synced |
860
|
|
|
|
|
|
|
as records.) |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
=cut |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
sub _record_resolution { |
865
|
0
|
|
|
0
|
|
|
my $self = shift; |
866
|
0
|
|
|
|
|
|
my ($change) = validate_pos(@_, { isa => 'Prophet::Change'}); |
867
|
|
|
|
|
|
|
|
868
|
0
|
0
|
|
|
|
|
return 1 if $self->record_exists( |
869
|
|
|
|
|
|
|
uuid => $self->uuid, |
870
|
|
|
|
|
|
|
type => '_prophet_resolution-' . $change->resolution_cas |
871
|
|
|
|
|
|
|
); |
872
|
|
|
|
|
|
|
|
873
|
|
|
|
|
|
|
$self->create_record( |
874
|
|
|
|
|
|
|
uuid => $self->uuid, |
875
|
|
|
|
|
|
|
type => '_prophet_resolution-' . $change->resolution_cas, |
876
|
|
|
|
|
|
|
props => { |
877
|
|
|
|
|
|
|
_meta => $change->change_type, |
878
|
0
|
|
|
|
|
|
map { $_->name => $_->new_value } $change->prop_changes |
|
0
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
} |
880
|
|
|
|
|
|
|
); |
881
|
|
|
|
|
|
|
} |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
=head2 routines dealing with integrating changesets into a replica |
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
=head3 record_changes L |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
Inside an edit (transaction), integrate all changes in this changeset |
888
|
|
|
|
|
|
|
and then call the _after_record_changes() hook. |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
=cut |
891
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
sub record_changes { |
893
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
894
|
0
|
|
|
|
|
|
my ($changeset) = validate_pos(@_, { isa => 'Prophet::ChangeSet'}); |
895
|
0
|
0
|
|
|
|
|
$self->_unimplemented ('record_changes') unless ($self->can_write_changesets); |
896
|
0
|
|
|
|
|
|
eval { |
897
|
0
|
|
|
|
|
|
local $SIG{__DIE__} = 'DEFAULT'; |
898
|
0
|
0
|
|
|
|
|
my $inside_edit = $self->current_edit ? 1 : 0; |
899
|
0
|
0
|
|
|
|
|
$self->begin_edit(source => $changeset) unless ($inside_edit); |
900
|
0
|
|
|
|
|
|
$self->integrate_changes($changeset); |
901
|
0
|
|
|
|
|
|
$self->_after_record_changes($changeset); |
902
|
0
|
0
|
|
|
|
|
$self->commit_edit() unless ($inside_edit); |
903
|
|
|
|
|
|
|
}; |
904
|
0
|
0
|
|
|
|
|
die($@) if ($@); |
905
|
|
|
|
|
|
|
} |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
=head3 integrate_changes L |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
This routine is called by L with a L |
910
|
|
|
|
|
|
|
object. It integrates all changes from that object into the current replica. |
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
All bookkeeping, such as opening and closing an edit, is done by |
913
|
|
|
|
|
|
|
L. |
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
If your replica type needs to play games to integrate multiple changes as a |
916
|
|
|
|
|
|
|
single record, this is what you'd override. |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
=cut |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
sub integrate_changes { |
921
|
0
|
|
|
0
|
1
|
|
my ($self, $changeset) = validate_pos( @_, {isa => 'Prophet::Replica'}, |
922
|
|
|
|
|
|
|
{ isa => 'Prophet::ChangeSet' } ); |
923
|
0
|
|
|
|
|
|
$self->integrate_change($_, $changeset) for ( $changeset->changes ); |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
} |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
=head2 integrate_change L |
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
Integrates the given change into the current replica. Used in |
930
|
|
|
|
|
|
|
L. |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
=cut |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
sub integrate_change { |
935
|
0
|
|
|
0
|
1
|
|
my ($self, $change) = validate_pos(@_, { isa => 'Prophet::Replica' }, |
936
|
|
|
|
|
|
|
{ isa => 'Prophet::Change' }, |
937
|
|
|
|
|
|
|
{ isa => 'Prophet::ChangeSet' } |
938
|
|
|
|
|
|
|
); |
939
|
|
|
|
|
|
|
|
940
|
0
|
|
|
|
|
|
my %new_props = map { $_->name => $_->new_value } $change->prop_changes; |
|
0
|
|
|
|
|
|
|
941
|
0
|
0
|
|
|
|
|
if ( $change->change_type eq 'add_file' ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
942
|
0
|
|
|
|
|
|
$self->log_debug("add_file: " .$change->record_type. " " .$change->record_uuid); |
943
|
0
|
|
|
|
|
|
$self->create_record( type => $change->record_type, uuid => $change->record_uuid, props => \%new_props); |
944
|
|
|
|
|
|
|
} elsif ( $change->change_type eq 'add_dir' ) { |
945
|
0
|
|
|
|
|
|
$self->log_debug("(IGNORED) add_dir: " .$change->record_type. " " .$change->record_uuid); |
946
|
|
|
|
|
|
|
} elsif ( $change->change_type eq 'update_file' ) { |
947
|
0
|
|
|
|
|
|
$self->log_debug("update_file: " .$change->record_type. " " .$change->record_uuid); |
948
|
0
|
|
|
|
|
|
$self->set_record_props( type => $change->record_type, uuid => $change->record_uuid, props => \%new_props); |
949
|
|
|
|
|
|
|
} elsif ( $change->change_type eq 'delete' ) { |
950
|
0
|
|
|
|
|
|
$self->log_debug("delete_file: " .$change->record_type. " " .$change->record_uuid); |
951
|
0
|
|
|
|
|
|
$self->delete_record( type => $change->record_type, uuid => $change->record_uuid); |
952
|
|
|
|
|
|
|
} else { |
953
|
0
|
|
|
|
|
|
Carp::confess( "Unknown change type: " . $change->change_type ); |
954
|
|
|
|
|
|
|
} |
955
|
|
|
|
|
|
|
} |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
=head3 record_integration_of_changeset L |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
This routine records the immediately upstream and original source |
960
|
|
|
|
|
|
|
uuid and sequence numbers for this changeset. Prophet uses this |
961
|
|
|
|
|
|
|
data to make sane choices about later replay and merge operations |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
=cut |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
sub record_integration_of_changeset { |
966
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
967
|
0
|
|
|
|
|
|
my ($changeset) = validate_pos( @_, { isa => 'Prophet::ChangeSet' } ); |
968
|
|
|
|
|
|
|
|
969
|
0
|
0
|
0
|
|
|
|
if ( $changeset->original_source_uuid ne $self->uuid |
970
|
|
|
|
|
|
|
&& ( $self->last_changeset_from_source( $changeset->original_source_uuid ) < $changeset->original_sequence_no ) |
971
|
|
|
|
|
|
|
) { |
972
|
0
|
|
|
|
|
|
$self->record_last_changeset_from_replica( |
973
|
|
|
|
|
|
|
$changeset->original_source_uuid => $changeset->original_sequence_no ); |
974
|
|
|
|
|
|
|
} |
975
|
0
|
0
|
|
|
|
|
if ( $changeset->source_uuid ) { |
976
|
0
|
0
|
|
|
|
|
if ( $self->last_changeset_from_source( $changeset->source_uuid ) < $changeset->sequence_no ) { |
977
|
0
|
|
|
|
|
|
$self->record_last_changeset_from_replica( $changeset->source_uuid => $changeset->sequence_no ); |
978
|
|
|
|
|
|
|
} |
979
|
|
|
|
|
|
|
} |
980
|
|
|
|
|
|
|
} |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
sub record_last_changeset_from_replica { |
983
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
984
|
0
|
|
|
|
|
|
my ($uuid, $sequence) = validate_pos(@_, 1,1); |
985
|
0
|
|
|
|
|
|
return $self->store_local_metadata( 'last-changeset-from-' . $uuid => $sequence ); |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
} |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
=head2 routines which need to be implemented by any Prophet backend store |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
=head3 uuid |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
Returns this replica's UUID. |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
=head3 create_record { type => $TYPE, uuid => $UUID, props => { key-value pairs } } |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
Create a new record of type C<$TYPE> with uuid C<$UUID> within the current |
998
|
|
|
|
|
|
|
replica. |
999
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
Sets the record's properties to the key-value hash passed in as the C |
1001
|
|
|
|
|
|
|
argument. |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
If called from within an edit, it uses the current edit. Otherwise it |
1004
|
|
|
|
|
|
|
manufactures and finalizes one of its own. |
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
=head3 delete_record {uuid => $UUID, type => $TYPE } |
1007
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
Deletes the record C<$UUID> of type C<$TYPE> from the current replica. |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
Manufactures its own new edit if C<$self->current_edit> is undefined. |
1011
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
=head3 set_record_props { uuid => $UUID, type => $TYPE, props => {hash of kv pairs }} |
1013
|
|
|
|
|
|
|
|
1014
|
|
|
|
|
|
|
Updates the record of type C<$TYPE> with uuid C<$UUID> to set each property |
1015
|
|
|
|
|
|
|
defined by the props hash. It does NOT alter any property not defined by the |
1016
|
|
|
|
|
|
|
props hash. |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
Manufactures its own current edit if none exists. |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
=head3 get_record_props { uuid => $UUID, type => $TYPE, root => $ROOT } |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
Returns a hashref of all properties for the record of type C<$TYPE> with uuid |
1023
|
|
|
|
|
|
|
C<$UUID>. |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
'root' is an optional argument which you can use to pass in an alternate |
1026
|
|
|
|
|
|
|
historical version of the replica to inspect. Code to look at the immediately |
1027
|
|
|
|
|
|
|
previous version of a record might look like: |
1028
|
|
|
|
|
|
|
|
1029
|
|
|
|
|
|
|
$handle->get_record_props( |
1030
|
|
|
|
|
|
|
type => $record->type, |
1031
|
|
|
|
|
|
|
uuid => $record->uuid, |
1032
|
|
|
|
|
|
|
root => $self->repo_handle->fs->revision_root( $self->repo_handle->fs->youngest_rev - 1 ) |
1033
|
|
|
|
|
|
|
); |
1034
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
=head3 record_exists {uuid => $UUID, type => $TYPE, root => $ROOT } |
1036
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
Returns true if the record in question exists and false otherwise. |
1038
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
=head3 list_records { type => $TYPE } |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
Returns a reference to a list of all the records of type $TYPE. |
1042
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
=head3 list_records |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
Returns a reference to a list of all the known types in your Prophet database. |
1046
|
|
|
|
|
|
|
|
1047
|
|
|
|
|
|
|
=head3 type_exists { type => $type } |
1048
|
|
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
Returns true if we have any records of type C<$TYPE>. |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
=head2 routines which need to be implemented by any _writable_ prophet backend store |
1052
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
=head2 optional routines which are provided for you to override with backend-store specific behaviour |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
=head3 _after_record_changes L |
1056
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
Called after the replica has integrated a new changeset but before closing the |
1058
|
|
|
|
|
|
|
current transaction/edit. |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
The SVN backend, for example, used this to record author metadata about this |
1061
|
|
|
|
|
|
|
changeset. |
1062
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
=cut |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
sub _after_record_changes { |
1066
|
0
|
|
|
0
|
|
|
return 1; |
1067
|
|
|
|
|
|
|
} |
1068
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
=head3 _set_original_source_metadata_for_current_edit |
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
Sets C and C for the current edit. |
1072
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
=cut |
1074
|
|
|
|
|
|
|
|
1075
|
|
|
|
0
|
|
|
sub _set_original_source_metadata_for_current_edit {} |
1076
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
=head2 helper routines |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
=cut |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
=head3 log $MSG |
1082
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
Logs the given message to C (but only if the C |
1084
|
|
|
|
|
|
|
environmental variable is set). |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
=cut |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
sub log { |
1089
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1090
|
0
|
|
|
|
|
|
my ($msg) = validate_pos(@_, 1); |
1091
|
0
|
0
|
|
|
|
|
Carp::confess unless ($self->app_handle); |
1092
|
0
|
|
|
|
|
|
$self->app_handle->log($msg); |
1093
|
|
|
|
|
|
|
} |
1094
|
|
|
|
|
|
|
|
1095
|
|
|
|
|
|
|
sub log_debug { |
1096
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
1097
|
0
|
|
|
|
|
|
my $msg = shift; |
1098
|
0
|
|
|
|
|
|
$self->app_handle->log_debug($self->display_name_for_replica.": " .$msg); |
1099
|
|
|
|
|
|
|
} |
1100
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
=head2 log_fatal $MSG |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
Logs the given message and dies with a stack trace. |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
=cut |
1106
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
sub log_fatal { |
1108
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
# always skip this fatal_error function when generating a stack trace |
1111
|
0
|
|
|
|
|
|
local $Carp::CarpLevel = $Carp::CarpLevel + 1; |
1112
|
0
|
0
|
|
|
|
|
if ( eval {$self->app_handle }) { |
|
0
|
|
|
|
|
|
|
1113
|
0
|
|
|
|
|
|
$self->app_handle->log_fatal(@_); |
1114
|
|
|
|
|
|
|
} else { |
1115
|
0
|
|
|
|
|
|
die join('',@_) ."\n"; |
1116
|
|
|
|
|
|
|
} |
1117
|
|
|
|
|
|
|
} |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
=head2 changeset_creator |
1120
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
The string to use as the creator of a changeset. |
1122
|
|
|
|
|
|
|
|
1123
|
|
|
|
|
|
|
=cut |
1124
|
|
|
|
|
|
|
|
1125
|
|
|
|
|
|
|
sub changeset_creator { |
1126
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1127
|
0
|
|
|
|
|
|
return $self->app_handle->current_user_email; |
1128
|
|
|
|
|
|
|
} |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
=head2 display_name_for_replica [uuid] |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
If the user has a "friendly" name for this replica, then use it. Otherwise, |
1133
|
|
|
|
|
|
|
display the replica's uuid. |
1134
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
If you pass in a uuid, it will be used instead of the replica's uuid. |
1136
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
=cut |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
sub display_name_for_replica { |
1140
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1141
|
0
|
|
0
|
|
|
|
my $uuid = shift || $self->uuid; |
1142
|
|
|
|
|
|
|
|
1143
|
0
|
0
|
|
|
|
|
return $uuid if !$self->app_handle; |
1144
|
|
|
|
|
|
|
|
1145
|
0
|
|
|
|
|
|
return $self->app_handle->display_name_for_replica($uuid); |
1146
|
|
|
|
|
|
|
} |
1147
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable(); |
1149
|
40
|
|
|
40
|
|
372
|
no Any::Moose; |
|
40
|
|
|
|
|
69
|
|
|
40
|
|
|
|
|
265
|
|
1150
|
|
|
|
|
|
|
|
1151
|
|
|
|
|
|
|
1; |