[Bps-public-commit] SD branch, master, updated. 0.70-18-g67a7332
sunnavy at bestpractical.com
sunnavy at bestpractical.com
Tue Sep 1 06:59:06 EDT 2009
The branch, master has been updated
via 67a73323e34a9015ead1445fe09b5cbe28203978 (commit)
from 08e358b1250973ac61baf99e3fef44a1f976f1e6 (commit)
Summary of changes:
lib/App/SD/Replica/lighthouse.pm | 121 +++++++++++++++++++
.../Replica/{github => lighthouse}/PullEncoder.pm | 125 ++++++++++----------
.../Replica/{github => lighthouse}/PushEncoder.pm | 74 ++++--------
3 files changed, 211 insertions(+), 109 deletions(-)
create mode 100644 lib/App/SD/Replica/lighthouse.pm
copy lib/App/SD/Replica/{github => lighthouse}/PullEncoder.pm (64%)
copy lib/App/SD/Replica/{github => lighthouse}/PushEncoder.pm (59%)
- Log -----------------------------------------------------------------
commit 67a73323e34a9015ead1445fe09b5cbe28203978
Author: sunnavy <sunnavy at bestpractical.com>
Date: Tue Sep 1 18:58:47 2009 +0800
add lighthouse lighthouse replica
diff --git a/lib/App/SD/Replica/lighthouse.pm b/lib/App/SD/Replica/lighthouse.pm
new file mode 100644
index 0000000..56bf6d5
--- /dev/null
+++ b/lib/App/SD/Replica/lighthouse.pm
@@ -0,0 +1,121 @@
+package App::SD::Replica::lighthouse;
+use Any::Moose;
+extends qw/App::SD::ForeignReplica/;
+
+use Params::Validate qw(:all);
+use Memoize;
+
+use URI;
+use Memoize;
+use Net::Lighthouse::Project;
+
+use Prophet::ChangeSet;
+
+use constant scheme => 'lighthouse';
+use constant pull_encoder => 'App::SD::Replica::lighthouse::PullEncoder';
+use constant push_encoder => 'App::SD::Replica::lighthouse::PushEncoder';
+
+has lighthouse => ( isa => 'Net::Lighthouse::Project', is => 'rw' );
+has remote_url => ( isa => 'Str', is => 'rw' );
+has account => ( isa => 'Str', is => 'rw' );
+has project => ( isa => 'Str', is => 'rw' );
+has query => ( isa => 'Str', is => 'rw' );
+
+our %PROP_MAP = ( state => 'status', title => 'summary' );
+
+sub BUILD {
+ my $self = shift;
+
+ my ( $auth, $account, $project ) =
+ $self->{url} =~ m{^lighthouse:(?:(.*)@)?(.*?)/(.*)}
+ or die
+ "Can't parse Github server spec. Expected
+ lighthouse:user:password\@account/project or\n"
+ ."lighthouse:token\@account/project.";
+ my $server = "http://$account.lighthouseapp.com";
+
+ my ( $email, $password, $token );
+ if ($auth) {
+ if ( $auth =~ /@/ ) {
+ ( $email, $password ) = split /:/, $auth;
+ }
+ else {
+ $token = $auth;
+ }
+ }
+
+ unless ( $token || $password ) {
+ if ($email) {
+ ( undef, $password ) =
+ $self->prompt_for_login( $server, $email );
+ }
+ else {
+ ($token) = $self->prompt_for_login($server);
+ }
+ }
+
+ $self->remote_url($server);
+ $self->account( $account );
+ $self->project( $project );
+
+ my $lighthouse = Net::Lighthouse::Project->new(
+ $email ? ( email => $email, password => $password ) : (),
+ $token ? ( token => $token ) : (),
+ account => $account,
+ );
+ $lighthouse->load( $project );
+ $self->lighthouse( $lighthouse );
+}
+
+
+sub get_txn_list_by_date {
+ my $self = shift;
+ my $ticket = shift;
+ my $ticket_obj = $self->lighthouse->ticket;
+ $ticket_obj->load($ticket);
+
+ my @txns = map {
+ {
+ id => $_->number,
+ creator => $_->creator_name,
+ created => $_->created_at->epoch,
+ }
+ }
+ sort { $b->created_at <=> $a->created_at } @{ $ticket_obj->versions };
+ return @txns;
+}
+
+sub foreign_username {
+ my $self = shift;
+ return $self->lighthouse->email;
+}
+
+sub uuid {
+ my $self = shift;
+ Carp::cluck "- can't make a uuid for this" unless ($self->remote_url && $self->account && $self->project );
+ return $self->uuid_for_url( join( '/', $self->remote_url, $self->project ) );
+}
+
+sub remote_uri_path_for_comment {
+ my $self = shift;
+ my $id = shift;
+ return "/comment/".$id;
+}
+
+sub remote_uri_path_for_id {
+ my $self = shift;
+ my $id = shift;
+ return "/ticket/".$id;
+}
+
+sub database_settings {
+ my $self = shift;
+ return {
+ project_name => $self->account . '/' . $self->project,
+ };
+
+}
+
+__PACKAGE__->meta->make_immutable;
+no Any::Moose;
+1;
diff --git a/lib/App/SD/Replica/lighthouse/PullEncoder.pm b/lib/App/SD/Replica/lighthouse/PullEncoder.pm
new file mode 100644
index 0000000..2f4555b
--- /dev/null
+++ b/lib/App/SD/Replica/lighthouse/PullEncoder.pm
@@ -0,0 +1,249 @@
+package App::SD::Replica::lighthouse::PullEncoder;
+use Any::Moose;
+extends 'App::SD::ForeignReplica::PullEncoder';
+
+use Params::Validate qw(:all);
+use Memoize;
+use Time::Progress;
+use DateTime;
+
+has sync_source => (
+ isa => 'App::SD::Replica::lighthouse',
+ is => 'rw',
+);
+
+my %PROP_MAP = %App::SD::Replica::lighthouse::PROP_MAP;
+
+sub ticket_id {
+ my $self = shift;
+ return shift->number;
+}
+
+=head2 translate_ticket_state
+
+=cut
+
+sub translate_ticket_state {
+ my $self = shift;
+ my $ticket = shift;
+
+ return $ticket;
+}
+
+=head2 find_matching_tickets QUERY
+
+Returns a array of all tickets found matching your QUERY hash.
+
+=cut
+
+sub find_matching_tickets {
+ my $self = shift;
+ my %query = (@_);
+ my $last_changeset_seen_dt = $self->_only_pull_tickets_modified_after()
+ || DateTime->from_epoch( epoch => 0 );
+ my @tickets = $self->sync_source->lighthouse->tickets;
+ my @updated = map { $_->load( $_->number ); $_ }
+ grep { $_->{updated_at} ge $last_changeset_seen_dt } @tickets;
+ return \@updated;
+}
+
+sub _only_pull_tickets_modified_after {
+ my $self = shift;
+
+ my $last_pull = $self->sync_source->upstream_last_modified_date();
+ return unless $last_pull;
+ my $before = App::SD::Util::string_to_datetime($last_pull);
+ $self->log_debug( "Failed to parse '" . $self->sync_source->upstream_last_modified_date() . "' as a timestamp. That means we have to sync ALL history") unless ($before);
+ return $before;
+}
+
+=head2 find_matching_transactions { ticket => $id, starting_transaction => $num }
+
+Returns a reference to an array of all transactions (as hashes) on ticket $id after transaction $num.
+
+=cut
+
+sub find_matching_transactions {
+ my $self = shift;
+ my %args = validate( @_, { ticket => 1, starting_transaction => 1 } );
+ my @raw_txns = @{$args{ticket}->versions};
+
+ my @txns;
+ for my $txn ( @raw_txns ) {
+ my $txn_date = $txn->created_at->epoch;
+
+ # Skip things we know we've already pulled
+ next if $txn_date < ( $args{'starting_transaction'} || 0 );
+
+ # Skip things we've pushed
+ next if (
+ $self->sync_source->foreign_transaction_originated_locally(
+ $txn_date, $args{'ticket'}->number
+ )
+ );
+
+ push @txns,
+ {
+ timestamp => $txn->created_at,
+ object => $txn,
+ serial => $txn->created_at->epoch,
+ $txn->created_at == $args{ticket}->created_at
+ ? ( is_create => 1 )
+ : (),
+ };
+ }
+
+ $self->sync_source->log_debug('Done looking at pulled txns');
+ return \@txns;
+}
+
+sub transcode_create_txn {
+ my $self = shift;
+ my $txn = shift;
+ my $ticket = $txn->{object};
+ my $ticket_uuid =
+ $self->sync_source->uuid_for_remote_id($ticket->number);
+ my $creator = $self->resolve_user_id_to( undef, $ticket->creator_name );
+ my $created = $ticket->created_at;
+ my $changeset = Prophet::ChangeSet->new(
+ {
+ original_source_uuid => $ticket_uuid,
+ original_sequence_no => 0,
+ creator => $creator,
+ created => $created->ymd . " " . $created->hms
+ }
+ );
+
+ my $change = Prophet::Change->new(
+ {
+ record_type => 'ticket',
+ record_uuid => $ticket_uuid,
+ change_type => 'add_file',
+ }
+ );
+
+ for my $prop (qw/title body state assigned_user_id milestone_id/) {
+ $change->add_prop_change(
+ name => $PROP_MAP{$prop} || $prop,
+ new => $ticket->$prop,
+ );
+ }
+
+ $change->add_prop_change(
+ name => $self->sync_source->uuid . '-id',
+ new => $ticket->number,
+ );
+
+ $changeset->add_change( { change => $change } );
+ return $changeset;
+}
+
+# we might get return:
+# 0 changesets if it was a null txn
+# 1 changeset if it was a normal txn
+# 2 changesets if we needed to to some magic fixups.
+
+sub transcode_one_txn {
+ my $self = shift;
+ my $txn_wrapper = shift;
+ my $ticket = shift;
+
+ my $txn = $txn_wrapper->{object};
+ if ( $txn_wrapper->{is_create} ) {
+ return $self->transcode_create_txn($txn_wrapper);
+ }
+
+ my $ticket_uuid =
+ $self->sync_source->uuid_for_remote_id( $ticket->number );
+
+ my $changeset = Prophet::ChangeSet->new(
+ {
+ original_source_uuid => $ticket_uuid,
+ original_sequence_no => $txn->created_at->epoch,
+ creator => $self->resolve_user_id_to( undef, $txn->creator_name ),
+ created => $txn->created_at->ymd . " " . $txn->created_at->hms
+ }
+ );
+
+ my $change = Prophet::Change->new(
+ {
+ record_type => 'ticket',
+ record_uuid => $ticket_uuid,
+ change_type => 'update_file'
+ }
+ );
+ my $diffable_attrs = $txn->diffable_attributes;
+ if (keys %$diffable_attrs) {
+ my %hash = (
+ ':tag' => 'tag',
+ ':milestone' => 'milestone_id',
+ ':assigned_user' => 'assigned_user_id',
+ ':state' => 'state',
+ );
+ for my $attr (keys %$diffable_attrs) {
+ next unless $hash{$attr};
+ my $method = $hash{$attr};
+ $change->add_prop_change(
+ name => $PROP_MAP{ $hash{$attr} } || $hash{$attr},
+ new => $txn->$method,
+ old => $diffable_attrs->{$attr},
+ );
+ }
+ }
+
+ $changeset->add_change( { change => $change } )
+ if $change->has_prop_changes;
+
+
+ $self->_include_change_comment( $changeset, $ticket_uuid, $txn );
+
+ return unless $changeset->has_changes;
+ return $changeset;
+}
+
+sub _include_change_comment {
+ my $self = shift;
+ my $changeset = shift;
+ my $ticket_uuid = shift;
+ my $txn = shift;
+
+ my $comment = $self->new_comment_creation_change();
+
+ if ( my $content = $txn->body ) {
+ if ( $content !~ /^\s*$/s ) {
+ $comment->add_prop_change(
+ name => 'created',
+ new => $txn->created_at->ymd . ' ' . $txn->created_at->hms,
+ );
+ $comment->add_prop_change(
+ name => 'creator',
+ new => $self->resolve_user_id_to( undef => $txn->creator_name ),
+ );
+ $comment->add_prop_change( name => 'content', new => $content );
+ $comment->add_prop_change(
+ name => 'content_type',
+ new => 'text/plain',
+ );
+ $comment->add_prop_change( name => 'ticket', new => $ticket_uuid, );
+ $changeset->add_change( { change => $comment } );
+ }
+ }
+}
+
+sub translate_prop_status {
+ my $self = shift;
+ my $status = shift;
+ return lc($status);
+}
+
+sub resolve_user_id_to {
+ my $self = shift;
+ shift;
+ my $id = shift;
+ return $id;
+# return $id . '@lighthouse';
+}
+
+__PACKAGE__->meta->make_immutable;
+no Any::Moose;
+1;
diff --git a/lib/App/SD/Replica/lighthouse/PushEncoder.pm b/lib/App/SD/Replica/lighthouse/PushEncoder.pm
new file mode 100644
index 0000000..b2ad521
--- /dev/null
+++ b/lib/App/SD/Replica/lighthouse/PushEncoder.pm
@@ -0,0 +1,132 @@
+package App::SD::Replica::lighthouse::PushEncoder;
+use Any::Moose;
+use Params::Validate;
+use Path::Class;
+
+has sync_source => (
+ isa => 'App::SD::Replica::lighthouse',
+ is => 'rw',
+);
+
+sub integrate_change {
+ my $self = shift;
+ my ( $change, $changeset ) = validate_pos(
+ @_,
+ { isa => 'Prophet::Change' },
+ { isa => 'Prophet::ChangeSet' }
+ );
+ my ( $id, $record );
+
+
+ return
+ if $self->sync_source->app_handle->handle->last_changeset_from_source(
+ $changeset->original_source_uuid ) >= $changeset->original_sequence_no;
+ my $before_integration = time();
+
+ eval {
+ if ( $change->record_type eq 'ticket'
+ and $change->change_type eq 'add_file' )
+ {
+ $id = $self->integrate_ticket_create( $change, $changeset );
+ $self->sync_source->record_remote_id_for_pushed_record(
+ uuid => $change->record_uuid,
+ remote_id => $id,
+ );
+ }
+ elsif (
+ ( $change->record_type eq 'ticket' )
+ || ( $change->record_type eq 'comment'
+ and $change->change_type eq 'add_file' )
+ )
+ {
+ $id = $self->integrate_ticket_update( $change, $changeset );
+ }
+ else {
+ $self->sync_source->log(
+ 'I have no idea what I am doing for ' . $change->record_uuid );
+ return;
+ }
+
+ $self->sync_source->record_pushed_transactions(
+ start_time => $before_integration,
+ ticket => $id,
+ changeset => $changeset,
+ );
+ };
+
+ if ( my $err = $@ ) {
+ $self->sync_source->log( "Push error: " . $err );
+ }
+
+ return $id;
+}
+
+sub integrate_ticket_update {
+ my $self = shift;
+ my ( $change, $changeset ) = validate_pos(
+ @_,
+ { isa => 'Prophet::Change' },
+ { isa => 'Prophet::ChangeSet' }
+ );
+
+ # Figure out the remote site's ticket ID for this change's record
+ my $remote_ticket_id =
+ $self->sync_source->remote_id_for_uuid( $change->record_uuid );
+ my $ticket = $self->sync_source->lighthouse->ticket;
+ $ticket->load( $remote_ticket_id );
+ my $attr = $self->_recode_props_for_integrate($change);
+ $ticket->update(
+ map { $_ => $attr->{$_} }
+ grep { exists $attr->{$_} }
+ qw/title body state assigned_user_id milestone_id/
+ );
+ return $remote_ticket_id;
+}
+
+sub integrate_ticket_create {
+ my $self = shift;
+ my ( $change, $changeset ) = validate_pos(
+ @_,
+ { isa => 'Prophet::Change' },
+ { isa => 'Prophet::ChangeSet' }
+ );
+
+ # Build up a ticket object out of all the record's attributes
+ my $ticket = $self->sync_source->lighthouse->ticket;
+ my $attr = $self->_recode_props_for_integrate($change);
+ $ticket->create(
+ map { $_ => $attr->{$_} }
+ grep { exists $attr->{$_} }
+ qw/title body state assigned_user_id milestone_id/
+ );
+ return $ticket->number;
+}
+
+sub _recode_props_for_integrate {
+ my $self = shift;
+ my ($change) = validate_pos( @_, { isa => 'Prophet::Change' } );
+
+ my %props = map { $_->name => $_->new_value } $change->prop_changes;
+ my %attr;
+
+ for my $key ( keys %props ) {
+ if ( $key eq 'summary' ) {
+ $attr{title} = $props{$key};
+ }
+ elsif ( $key eq 'status' ) {
+ $attr{state} = $props{$key};
+ }
+ elsif ( $key eq 'body' ) {
+ $attr{$key} = $props{$key} || '[no body]';
+ }
+ else {
+ $attr{$key} = $props{$key};
+ }
+ }
+ return \%attr;
+}
+
+__PACKAGE__->meta->make_immutable;
+no Any::Moose;
+
+1;
-----------------------------------------------------------------------
More information about the Bps-public-commit
mailing list