use strict; package LJR::Distributed; my $warn = sub { print join ("\n", @_); print "\n"; }; my $err = sub { if (ref($_[0])) { my $dbh = shift; $dbh->rollback; } my %res = (); my $cstack = "\ncallstack:"; my $i = 0; while ( 1 ) { my $tfunc = (caller($i))[3]; if ($tfunc && $tfunc ne "") { if ($tfunc !~ /\_\_ANON\_\_/) { $cstack .= " " . $tfunc; } $i = $i + 1; } else { last; } } $res{"err"} = 1; $res{"errtext"} = join ("\n", @_); $res{"errtext"} .= $cstack; return \%res; }; # # created from LJ::Talk::Post::enter_comment # sub create_imported_comment { my ($journalu, $parent, $item, $comment) = @_; return $err->("Invalid user object passed.") unless LJ::isu($journalu); my $partid = $parent->{talkid}; my $itemid = $item->{jitemid}; my $posterid = $comment->{u} ? $comment->{u}{userid} : 0; # TODO: change this to be remote server specific my $time_float = "-3"; my $comment_time; if ($comment->{datetime}) { $comment_time = "'" . $comment->{datetime} . "' + INTERVAL " . $time_float . " HOUR"; } else { # deleted comments $comment_time = "'1970-01-01T00:00:00Z'"; } my $jtalkid = LJ::alloc_user_counter($journalu, "T"); return $err->("Could not generate a talkid necessary to import comment.") unless $jtalkid; my $errstr; $journalu->talk2_do( "L", $itemid, \$errstr, "INSERT INTO talk2 ". "(journalid, jtalkid, nodetype, nodeid, parenttalkid, posterid, datepost, state) ". "VALUES (?,?,'L',?,?,?," . $comment_time . ",?)", $journalu->{userid}, $jtalkid, $itemid, $partid, $posterid, $comment->{state} ); return $err->("error creating imported comment: " . $errstr) if ($errstr); $comment->{talkid} = $jtalkid; # add to poster's talkleft table, or the xfer place if ($posterid) { my $table; my $db = LJ::get_cluster_master($comment->{u}); if ($db) { # remote's cluster is writable $table = "talkleft"; } else { # log to global cluster, another job will move it later. $db = LJ::get_db_writer(); $table = "talkleft_xfp"; } my $pub = $item->{'security'} eq "public" ? 1 : 0; if ($db) { $db->do( "INSERT INTO $table (userid, posttime, journalid, nodetype, ". "nodeid, jtalkid, publicitem) VALUES (?, UNIX_TIMESTAMP(" . $comment_time . "), " . "?, 'L', ?, ?, ?)", undef, $posterid, $journalu->{userid}, $itemid, $jtalkid, $pub); return $err->($db->errstr) if $db->err; } else { # both primary and backup talkleft hosts down. can't do much now. } } $journalu->do( "INSERT INTO talktext2 (journalid, jtalkid, subject, body) ". "VALUES (?, ?, ?, ?)", undef, $journalu->{userid}, $jtalkid, $comment->{subject}, LJ::text_compress($comment->{body}) ); return $err->($journalu->errstr) if $journalu->err; my %talkprop = %{$comment->{props}}; # propname -> value if (%talkprop) { my $values; my $hash = {}; foreach (keys %talkprop) { my $p = LJ::get_prop("talk", $_); next unless $p; $hash->{$_} = $talkprop{$_}; my $tpropid = $p->{'tpropid'}; my $qv = $journalu->quote($talkprop{$_}); $values .= "($journalu->{'userid'}, $jtalkid, $tpropid, $qv),"; } if ($values) { chop $values; $journalu->do("INSERT INTO talkprop2 (journalid, jtalkid, tpropid, value) VALUES $values"); return $err->($journalu->errstr) if $journalu->err; } } # update the "replycount" summary field of the log table if ($comment->{state} eq 'A' || $comment->{state} eq 'F') { LJ::replycount_do($journalu, $itemid, "incr"); } # update the "hasscreened" property of the log item if needed if ($comment->{state} eq 'S') { LJ::Talk::screenedcount_do($journalu, $itemid, "incr"); } return $comment; } sub create_imported_comments { my ($ru, $local_user, $throttle_num, $throttle_sec) = @_; if (!$throttle_num) { $throttle_num = 1000; } if (!$throttle_sec) { $throttle_sec = 20; } my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; # local journal where comment is to be created my $journalu = LJ::load_user($local_user, 1); $ru = LJR::Distributed::get_cu_field($ru, "cached_comments_maxid"); $ru->{cached_comments_maxid} = 0 if not defined $ru->{cached_comments_maxid}; $ru = LJR::Distributed::remote_local_assoc($ru, $journalu); return $err->("error while getting remote-local association: " . $ru->{errtext}) if $ru->{err}; # check if someone deleted imported comments, # delete inconsistent ljr_remote_comments entries and # update corresponding ljr_remote_users.created_comments_maxid my $sth_cc = $dbr->prepare("SELECT count(*) from ljr_remote_comments l LEFT JOIN talk2 t on l.local_journalid = t.journalid and l.local_jtalkid = t.jtalkid WHERE l.local_journalid = ? and t.journalid is NULL;"); $sth_cc->execute($journalu->{"userid"}); my $deleted_num; if (($deleted_num = $sth_cc->fetchrow_array) && $deleted_num && $deleted_num gt 0) { my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; $dbh->do( "DELETE ljr_remote_comments FROM ljr_remote_comments LEFT JOIN talk2 ON ljr_remote_comments.local_journalid = talk2.journalid AND ljr_remote_comments.local_jtalkid = talk2.jtalkid WHERE ljr_remote_comments.local_journalid = ? and talk2.journalid is NULL;", undef, $journalu->{"userid"}); return $err->($dbh->errstr) if $dbh->err; $dbh->do ("UPDATE ljr_remote_users SET created_comments_maxid = created_comments_maxid - ? WHERE ru_id=? and local_journalid=?", undef, $deleted_num, $ru->{"ru_id"}, $journalu->{"userid"} ); return $err->($dbh->errstr) if $dbh->err; $ru->{"created_comments_maxid"} = $ru->{"created_comments_maxid"} - $deleted_num; } $sth_cc->finish; # if we haven't posted all the cached comments, do it now if ($ru->{"created_comments_maxid"} < $ru->{"cached_comments_maxid"}) { LJ::load_props("talk"); return $err->("Can't load talkprops.") unless $LJ::CACHE_PROP{talk}; my $sth = $dbr->prepare(" SELECT ljr_cached_comments.* FROM ljr_cached_comments LEFT JOIN ljr_remote_comments ON ljr_cached_comments.cc_id = ljr_remote_comments.cc_id and ljr_remote_comments.local_journalid = ? WHERE ljr_cached_comments.ru_id = ? and ljr_remote_comments.cc_id is NULL "); $sth->execute($journalu->{"userid"}, $ru->{"ru_id"}); my $up; # local user which owns imported comment my $item; # local journal entry where the comment is to be imported my $parent; # parent comment (0 for comments to the entry) my $comment; # comment being created my $i = 0; # counter while (my $r = $sth->fetchrow_hashref) { if ($r->{posterid}) { $up = LJR::Distributed::get_cached_user({ru_id => $r->{ru_id}}); $up->{ru_id} = 0; $up->{userid} = $r->{posterid}; $up->{username} = ""; $up = LJR::Distributed::get_cached_user($up); $up = LJR::Distributed::get_cu_field($up, "local_commenterid"); $up = LJ::load_userid ($up->{local_commenterid}); } else { $up = undef; } $item = LJR::Distributed::get_local_itemid ($journalu, $r->{ru_id}, $r->{jitemid}); if (!$item->{itemid}) { $warn->("Can't find corresponding local entry while importing " . $journalu->{name} . " (remote entry " . $r->{ru_id} . ":" . $r->{jitemid} . ")."); next; } $item = $item->{item}; if ($r->{"parentid"}) { $parent = LJR::Distributed::get_local_commentid( $journalu->{"userid"}, $r->{"ru_id"}, $r->{"parentid"}); if (!$parent->{"talkid"}) { $warn->( "Can't find corresponding parent comment while importing " . $journalu->{"name"} . " (remote parent id: " . $r->{"parentid"} . "), " . "attaching to the local entry (" . $item->{"jitemid"} . ")" ); $parent->{"talkid"} = 0; } } else { $parent->{"talkid"} = 0; } my $sth1 = $dbr->prepare("SELECT tpropid, value FROM ljr_cached_comprops WHERE cc_id=?"); $sth1->execute($r->{cc_id}); my %props = (); while (my $p = $sth1->fetchrow_hashref) { $props{$LJ::CACHE_PROPID{talk}->{$p->{tpropid}}->{name}} = $p->{value}; } $sth1->finish(); $comment = { u => $up, subject => $r->{subject}, body => $r->{body}, state => $r->{state}, datetime => $r->{date}, props => \%props, }; $comment = LJR::Distributed::create_imported_comment($journalu, $parent, $item, $comment); return $err->("error while creating imported comment: " . $comment->{errtext}) if $comment->{err}; my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; $dbh->do( "INSERT INTO ljr_remote_comments VALUES (?,?,?)", undef, $r->{cc_id}, $journalu->{userid}, $comment->{talkid}); return $err->($dbh->errstr) if $dbh->err; $dbh->do("UPDATE ljr_remote_users SET created_comments_maxid = ? WHERE ru_id=? and local_journalid=?", undef, $r->{commentid}, $ru->{ru_id}, $journalu->{userid} ); return $err->($dbh->errstr) if $dbh->err; $i = $i + 1; if ($i % $throttle_num == 0) { sleep $throttle_sec; } } $sth->finish(); } return $ru; } sub cache_comment { my ($c) = @_; if ($c->{id}) { $c->{commentid} = $c->{id}; } if (!$c->{state}) { $c->{state} = "A"; } return $err->("cache_comment: no ru_id specified.") unless $c->{ru_id}; return $err->("cache_comment: no comment id specified.") unless $c->{commentid}; return $err->("cache_comment: no jitemid specified.") unless $c->{jitemid}; if ($c->{state} ne "D") { return $err->("cache_comment: no body specified.") unless $c->{body}; return $err->("cache_comment: no date specified.") unless $c->{date}; } else { $c->{body} = "" unless $c->{body}; $c->{date} = "" unless $c->{date}; } if (!$c->{posterid}) { $c->{posterid} = 0; } if (!$c->{parentid}) { $c->{parentid} = 0; } if (!$c->{subject}) { $c->{subject} = ""; } my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; my $cc_id1 = $dbr->selectrow_array( "SELECT cc_id FROM ljr_cached_comments " . "WHERE ru_id=? and commentid=?", undef, $c->{ru_id}, $c->{commentid}); if ($cc_id1) { my $existing_comment = $dbr->selectrow_hashref( "SELECT * FROM ljr_cached_comments " . "WHERE ru_id=? and commentid=?", undef, $c->{ru_id}, $c->{commentid}); # it just works (somehow clears some UTF8 flag) $c->{body} = pack('C*', unpack('C*', $c->{body})) if $c->{body}; $c->{subject} = pack('C*', unpack('C*', $c->{subject})) if $c->{subject}; if ( $c->{jitemid} != $existing_comment->{jitemid} || $c->{parentid} != $existing_comment->{parentid} || $c->{subject} ne $existing_comment->{subject} || $c->{body} ne $existing_comment->{body} || $c->{date} ne $existing_comment->{date} || ($c->{posterid} != 0 && $c->{posterid} != $existing_comment->{posterid}) ) { my $ru = LJR::Distributed::get_cached_user({ru_id => $c->{ru_id}}); return $err->("There already exists different comment id=$c->{commentid} for user $ru->{username} (ru_id = $ru->{ru_id}).!") ; } my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; $dbh->do( "UPDATE ljr_cached_comments " . "SET posterid=?, state=? " . "WHERE ru_id=? and commentid=?", undef, $c->{posterid}, $c->{state}, $c->{ru_id}, $c->{commentid}); return $err->($dbh->errstr) if $dbh->err; } else { my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; # start transaction $dbh->begin_work; return $err->($dbh->errstr) if $dbh->err; $dbh->do( "INSERT INTO ljr_cached_comments " . "(ru_id, commentid, posterid, state, jitemid, parentid, " . "subject, body, date) VALUES " . "(?,?,?,?,?,?,?,?,?)", undef, $c->{ru_id}, $c->{commentid}, $c->{posterid}, $c->{state}, $c->{jitemid}, $c->{parentid}, $c->{subject}, $c->{body}, $c->{date}); return $err->($dbh, $dbh->errstr) if $dbh->err; # get newly cached comment id $c->{cc_id} = $dbh->{mysql_insertid}; # save props LJ::load_props("talk"); return $err->($dbh, "Can't load talkprops.") unless $LJ::CACHE_PROP{talk}; if ($c->{props}) { foreach my $k (keys %{$c->{props}}) { if (${$LJ::CACHE_PROP{talk}}{$k}->{tpropid}) { $dbh->do( "INSERT INTO ljr_cached_comprops VALUES (?, ?, ?) ", undef, $c->{cc_id}, ${$LJ::CACHE_PROP{talk}}{$k}->{tpropid}, $c->{props}->{$k} ); return $err->($dbh, "Error caching property \"" . $k . "\": " . $dbh->errstr) if $dbh->err; } } } $dbh->commit; return $err->($dbh->errstr) if $dbh->err; } return $c; } # # copied from LJ::alloc_global_counter # sub alloc_global_counter { my ($dom, $recurse) = @_; return $err->("alloc_global_counter: invalid domain!") unless $dom =~ /^[I]$/; my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; my $newmax; my $uid = 0; # userid is not needed, we just use '0' my $rs = $dbh->do( "UPDATE ljr_counter SET max=LAST_INSERT_ID(max+1) WHERE journalid=? AND area=?", undef, $uid, $dom); if ($rs > 0) { $newmax = $dbh->selectrow_array("SELECT LAST_INSERT_ID()"); return $newmax; } return undef if $recurse; # no prior counter rows - initialize one. if ($dom eq "I") { $newmax = 0; } $newmax += 0; $dbh->do("INSERT IGNORE INTO ljr_counter (journalid, area, max) VALUES (?,?,?)", undef, $uid, $dom, $newmax) or return undef; return LJR::Distributed::alloc_global_counter($dom, 1); } # # copied from LJ::User::load_identity_user # sub get_imported_user { my ($ru) = @_; return $err->("remote_serverid is not specified.") unless $ru->{serverid}; return $err->("remote_username is not specified.") unless $ru->{username}; my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; $ru = LJR::Distributed::get_remote_server_byid($ru); return $err->("Server " . $ru->{serverid} . "doesn't exist!") unless $ru->{"servername"}; my $serverurl = $ru->{servername}; my $identity = $serverurl . "/users/" . $ru->{username}; my $uid = $dbr->selectrow_array( "SELECT userid FROM identitymap WHERE idtype=? AND identity=?", undef, "G", $identity); if (!$uid) { my $impuser = 'imp_' . LJR::Distributed::alloc_global_counter('I'); $uid = LJ::create_account({ caps => undef, user => $impuser, name => $impuser, journaltype => 'I', imported => 1, }); return $err->("can't create account!") unless $uid; my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; $dbh->do( "INSERT INTO identitymap (idtype, identity, userid) VALUES (?,?,?)", undef, "G", $identity, $uid); return $err->($dbh->errstr) if $dbh->err; $uid = $dbr->selectrow_array( "SELECT userid FROM identitymap WHERE idtype=? AND identity=?", undef, "G", $identity); return $err->("can't get identity userid for " . $identity) unless $uid; } $ru->{commenterid} = $uid; return $ru; } # # gets ljr_cached_users field (identified with ru_id) # sub get_cu_field { my ($ru, $field) = @_; my $truid; if (ref($ru)) { $truid = $ru->{ru_id} } else { $truid = $ru; } return $err->("get_cu_field: ru_id is not specified.") unless $truid; return $err->("get_cu_field: field is not specified.") unless $field; my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; my $tvalue = $dbr->selectrow_array( "SELECT $field FROM ljr_cached_users " . "WHERE ru_id=?", undef, $truid); if (ref($ru)) { $ru->{$field} = $tvalue; return $ru; } else { return $tvalue; } } # # currently overwrites everything. maybe it shouldn't. # sub set_cu_field { my ($ru, $field, $value) = @_; return $err->("set_cu_field: no ru_id in ru hashref.") unless $ru->{ru_id}; return $err->("set_cu_field: field is not specified (ru_id = $ru->{ru_id}).") unless $field; return $err->("set_cu_field: field value is not specified (field = $field).") unless defined($value); my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; my $existing_field_value = $dbr->selectrow_array( "SELECT $field FROM ljr_cached_users WHERE ru_id = ?", undef, $ru->{ru_id}); if ( defined($existing_field_value) && defined($value) && $existing_field_value != $value || $existing_field_value && !defined($value) || !defined($existing_field_value) && $value ) { my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; if ($existing_field_value) { $warn->( "ljr_cached_users: overwriting " . $field . " [" . $existing_field_value . "] " . "with [ " . $value . " ] " . "for ru_id = " . $ru->{ru_id}); } $dbh->do( "UPDATE ljr_cached_users SET $field = ? WHERE ru_id = ?", undef, $value, $ru->{ru_id}); return $err->("error updating ljr_cached_users.${field}: " . $dbh->errstr) if $dbh->err; } $ru->{$field} = $value; return $ru; } sub match_remote_server { my ($remote_server) = @_; my %res = (); return $err->("no server name specified!") unless $remote_server; my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; if ($remote_server =~ /.*\.(\w+\.\w+)\/?/) { $remote_server = $1; } my ($serverid, $servername, $servertype) = $dbr->selectrow_array( "SELECT remote_serverid, canonical_url, blog_type FROM ljr_remote_servers " . "WHERE canonical_url like '%" . $remote_server . "'"); $res{"serverid"} = $serverid; $res{"servertype"} = $servertype; $res{"servername"} = $servername; return \%res; } # # name: LJR::Distributed::get_remote_server # des: get remote server identification at the local site # returns: $hashref->{serverid} or $hashref->{err} and $hashref->{errtext} # args: remote_server # des-remote_server: canonical name of the remote server # sub get_remote_server { my ($remote_server) = @_; my %res = (); return $err->("no server name specified!") unless $remote_server; my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; # TODO: maybe we'll have to change this to support https or smth else? # TODO: note that we'll have to change LJ::Simple also, which currently # TODO: work only with http:// if ($remote_server !~ /^http\:\/\//) { $remote_server = "http://" . $remote_server; } my ($serverid, $servertype) = $dbr->selectrow_array( "SELECT remote_serverid, blog_type FROM ljr_remote_servers " . "WHERE canonical_url=?", undef, $remote_server); if (!$serverid) { my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; $dbh->do( "INSERT INTO ljr_remote_servers (canonical_url) VALUES (?)", undef, $remote_server); return $err->($dbh->errstr) if $dbh->err; $serverid = $dbh->{mysql_insertid}; return $err->("Can't get serverid!") unless $serverid; } $res{"serverid"} = $serverid; $res{"servertype"} = $servertype; $res{"servername"} = $remote_server; return \%res; } sub get_remote_server_byid { my ($ru) = @_; if ($ru->{"serverid"}) { my $dbr = LJ::get_db_reader(); return $err->("No database reader available!") unless $dbr; ($ru->{"servername"}, $ru->{"servertype"}) = $dbr->selectrow_array( "SELECT canonical_url, blog_type " . "FROM ljr_remote_servers WHERE remote_serverid=?", undef, $ru->{serverid}); } return $ru; } sub remote_local_assoc { my ($ru, $u) = @_; return $err->("remote_local_assoc: no ru_id in ru hashref.") unless $ru->{ru_id}; return $err->("remote_local_assoc: no userid in user hashref.") unless $u->{userid}; my $dbr = LJ::get_db_reader(); return $err->("No database reader available!") unless $dbr; my ($r) = $dbr->selectrow_array( "SELECT count(*) FROM ljr_remote_users " . "WHERE ru_id=? and local_journalid=?", undef, $ru->{ru_id}, $u->{userid}); if ($r) { $ru->{created_comments_maxid} = $dbr->selectrow_array( "SELECT created_comments_maxid FROM ljr_remote_users " . "WHERE ru_id=? and local_journalid=?", undef, $ru->{ru_id}, $u->{userid}); $ru->{assoc_existed} = 1; } else { $ru->{assoc_existed} = 0; $ru->{created_comments_maxid} = 0; my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; $dbh->do( "INSERT ljr_remote_users SET ru_id = ?, local_journalid = ?", undef, $ru->{ru_id}, $u->{userid}, ); return $err->($dbh->errstr) if $dbh->err; } return $ru; } sub change_identity { my ($dbh, $remote_serverid, $old_username, $new_username) = @_; my $dbr = LJ::get_db_reader(); return $err->("No database reader available!") unless $dbr; my ($serverurl, $servertype) = $dbr->selectrow_array( "SELECT canonical_url, blog_type FROM ljr_remote_servers " . "WHERE remote_serverid=?", undef, $remote_serverid); return $err->("Server " . $remote_serverid . "doesn't exist!") unless $serverurl; my $old_identity = $serverurl . "/users/" . $old_username; my $new_identity = $serverurl . "/users/" . $new_username; my $uid = $dbr->selectrow_array( "SELECT userid FROM identitymap WHERE idtype=? AND identity=?", undef, "G", $old_identity); return $err->("can't get identity userid for " . $old_identity) unless ($uid); $dbh->do( "UPDATE identitymap SET identity = ? WHERE userid = ?", undef, $new_identity, $uid); return $err->("Error changing identity: " . $dbh->errstr) if $dbh->err; return {'err' => 0}; } # # name: LJR::Distributed::get_cached_user # des: get cached user hashref # des: $ru->{ru_id} # des: $ru->{serverid} # des: $ru->{userid} # des: $ru->{username} # des: $ru->{type} # returns: $ru or $ru->{err} and $ru->{errtext} # args: $ru->{serverid} && ($ru->{userid} and/or $ru->{username}) # sub get_cached_user { my ($ru) = @_; my $dbr = LJ::get_db_reader(); return $err->("No database reader available!") unless $dbr; my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; if ($ru->{'ru_id'}) { ($ru->{'serverid'}, $ru->{'userid'}, $ru->{'username'}) = $dbr->selectrow_array( "SELECT remote_serverid, remote_userid, remote_username " . "FROM ljr_cached_users WHERE ru_id=?", undef, $ru->{'ru_id'}); } else { return $err->("remote_serverid is not specified.") unless $ru->{'serverid'}; my $remote_type; if ($ru->{'type'}) { $remote_type = $ru->{'type'} } else { $remote_type = "P"; } if ($ru->{'username'} && $ru->{'userid'}) { my $update_ljr_cached_users = sub { $dbh->do( "UPDATE ljr_cached_users SET remote_userid = ?, remote_username = ? WHERE ljr_cached_users.ru_id = ?", undef, $ru->{'userid'}, $ru->{'username'}, $ru->{'ru_id'}); return $err->($dbh, $dbh->errstr) if $dbh->err; $ru->{'type'} = $dbr->selectrow_array( "SELECT remote_type FROM ljr_cached_users WHERE ru_id = ?", undef, $ru->{'ru_id'}); return; }; my $rename_remote_user = sub { my ($ru_id, $old_username, $new_username) = @_; my $rename_seq = $dbr->selectrow_array( "SELECT max(rename_seq) FROM ljr_remote_renamed WHERE ru_id=?", undef, $ru_id); $rename_seq++; # save old username $dbh->do( "INSERT ljr_remote_renamed (ru_id, rename_seq, old_username, new_username) VALUES (?, ?, ?, ?)", undef, $ru_id, $rename_seq, $old_username, $new_username); return $err->($dbh, $dbh->errstr) if $dbh->err; # update identitymap my $r = change_identity($dbh, $ru->{'serverid'}, $old_username, $new_username); return $err->($dbh, $r->{"errtext"}) if $r->{"err"}; return; }; my $reid_remote_user = sub { my ($remote_username, $old_ru_id, $new_ru_id) = @_; my $reid_seq = $dbr->selectrow_array( "SELECT max(reid_seq) FROM ljr_remote_reided " . "WHERE remote_serverid=? and remote_username=?", undef, $ru->{'serverid'}, $remote_username); $reid_seq++; $dbh->do( "INSERT INTO ljr_remote_reided (remote_serverid, remote_username, reid_seq, old_ru_id, new_ru_id) VALUES (?, ?, ?, ?, ?)", undef, $ru->{'serverid'}, $remote_username, $reid_seq, $old_ru_id, $new_ru_id); return $err->($dbh, $dbh->errstr) if $dbh->err; return; }; my ($username_ru_id, $username_userid, $username_local_commenterid) = $dbr->selectrow_array( "SELECT ru_id, remote_userid, local_commenterid FROM ljr_cached_users " . "WHERE remote_serverid=? and remote_username=?", undef, $ru->{'serverid'}, $ru->{'username'}); my ($userid_ru_id, $userid_username, $userid_local_commenterid) = $dbr->selectrow_array( "SELECT ru_id, remote_username, local_commenterid FROM ljr_cached_users " . "WHERE remote_serverid=? and remote_userid=?", undef, $ru->{'serverid'}, $ru->{'userid'}); my $r; if ($username_ru_id && $userid_ru_id) { if ($username_userid && $userid_username) { if ($username_ru_id ne $userid_ru_id) { $warn->("get_cached_user: " . $ru->{'username'} . " points to ${username_userid}, " . $ru->{'userid'} . " points to ${userid_username}; " . "trying to solve."); $dbh->begin_work; $r = $reid_remote_user->($ru->{'username'}, $username_ru_id, $userid_ru_id); return $err->("gcu1.1: " . $r->{'errtext'}) if $r->{'err'}; my $has_ex = 1; my $ex_suff; my $ex_name; while ($has_ex) { $ex_name = "ex_" . $ru->{'username'} . ($ex_suff ? ("_" . $ex_suff) : ""); $has_ex = $dbh->selectrow_array( "SELECT count(remote_username) FROM ljr_cached_users WHERE remote_serverid=? and remote_username=?", undef, $ru->{'serverid'}, $ex_name); $ex_suff = $ex_suff + 1; } $r = $rename_remote_user->($username_ru_id, $ru->{'username'}, $ex_name); return $err->("gcu1.2: " . $ru->{'username'} . ": " . $r->{'errtext'}) if $r->{'err'}; $dbh->do( "UPDATE ljr_cached_users SET remote_username = ? WHERE ljr_cached_users.ru_id = ?", undef, $ex_name, $username_ru_id); return $err->($dbh, "gcu1.3: ${ex_name}:" . $dbh->errstr) if $dbh->err; $r = $rename_remote_user->($userid_ru_id, $userid_username, $ru->{'username'}); return $err->("gcu1.4: " . $r->{'errtext'}) if $r->{'err'}; $dbh->do( "UPDATE ljr_cached_users SET remote_username = ? WHERE ljr_cached_users.ru_id = ?", undef, $ru->{'username'}, $userid_ru_id); return $err->($dbh, "gcu1.5: " . $dbh->errstr) if $dbh->err; $ru->{'ru_id'} = $userid_ru_id; $r = $update_ljr_cached_users->(); return $err->("gcu1.6: " . $r->{'errtext'}) if $r->{'err'}; $dbh->commit; } else { $ru->{'ru_id'} = $username_ru_id; $r = $update_ljr_cached_users->(); return $err->("gcu1.7: " . $r->{'errtext'}) if $r->{'err'}; } } elsif (! $username_userid && ! $username_local_commenterid && $userid_username) { $dbh->begin_work; $ru->{'ru_id'} = $userid_ru_id; # ljr_cached_users for $ru->{'username'} doesn't have remote_userid # it's bogus. deleting it. $dbh->do ("DELETE FROM ljr_cached_users WHERE ru_id=?", undef, $username_ru_id); return $err->($dbh, "gcu1.2: " . $dbh->errstr) if $dbh->err; if ($userid_username ne $ru->{'username'}) { $r = $rename_remote_user->($ru->{'ru_id'}, $userid_username, $ru->{'username'}); return $err->("gcu1.2: " . $r->{'errtext'}) if $r->{'err'}; } $r = $update_ljr_cached_users->(); return $err->("gcu1.2: " . $r->{'errtext'}) if $r->{'err'}; $dbh->commit; } else { return $err->("ljr_cached_users inconsistency type 1 for $ru->{username} and $ru->{userid}.") } } elsif ($username_ru_id) { $dbh->begin_work; if ($username_userid && $username_userid ne $ru->{'userid'}) { my $has_ex = 1; my $ex_suff; my $ex_name; while ($has_ex) { $ex_name = "ex_" . $ru->{'username'} . ($ex_suff ? ("_" . $ex_suff) : ""); $has_ex = $dbr->selectrow_array( "SELECT count(remote_username) FROM ljr_cached_users WHERE remote_serverid=? and remote_username=?", undef, $ru->{'serverid'}, $ex_name); $ex_suff = $ex_suff + 1; } $r = $rename_remote_user->($username_ru_id, $ru->{'username'}, $ex_name); return $err->("gcu2.1: " . $r->{'errtext'}) if $r->{'err'}; $dbh->do( "UPDATE ljr_cached_users SET remote_username = ? WHERE ljr_cached_users.ru_id = ?", undef, $ex_name, $username_ru_id); return $err->($dbh, "gcu2.2: " . $dbh->errstr) if $dbh->err; $dbh->do( "INSERT INTO ljr_cached_users (remote_serverid, remote_username, remote_userid, remote_type) VALUES (?, ?, ?, ?)", undef, $ru->{'serverid'}, $ru->{'username'}, $ru->{'userid'}, $remote_type); return $err->($dbh, "gcu2.3: " . $dbh->errstr) if $dbh->err; $ru->{'ru_id'} = $dbh->{mysql_insertid}; $r = $reid_remote_user->($ru->{'username'}, $username_ru_id, $ru->{'ru_id'}); return $err->("gcu2.4: " . $r->{'errtext'}) if $r->{'err'}; } else { $ru->{'ru_id'} = $username_ru_id; } $r = $update_ljr_cached_users->(); return $err->("gcu2.5: " . $r->{'errtext'}) if $r->{'err'}; $dbh->commit; } elsif ($userid_ru_id) { $dbh->begin_work; $ru->{'ru_id'} = $userid_ru_id; if ($userid_username && $userid_username ne $ru->{'username'}) { $r = $rename_remote_user->($ru->{'ru_id'}, $userid_username, $ru->{'username'}); return $err->("gcu3: " . $r->{'errtext'}) if $r->{'err'}; } $r = $update_ljr_cached_users->(); return $err->("gcu3: " . $r->{'errtext'}) if $r->{'err'}; $dbh->commit; } else { $dbh->do( "INSERT ljr_cached_users (remote_serverid, remote_userid, remote_username, remote_type) VALUES (?, ?, ?, ?)", undef, $ru->{'serverid'}, $ru->{'userid'}, $ru->{'username'}, $remote_type); return $err->("gcu4: " . $dbh->errstr) if $dbh->err; $ru->{'ru_id'} = $dbh->{mysql_insertid}; $ru->{'type'} = $remote_type; } } elsif ($ru->{'username'} && !$ru->{'userid'}) { my ($ru_id_1, $ljr_userid, $ljr_type); ($ru_id_1, $ljr_userid, $ljr_type) = $dbr->selectrow_array( "SELECT ru_id, remote_userid, remote_type FROM ljr_cached_users " . "WHERE remote_serverid=? and remote_username=?", undef, $ru->{'serverid'}, $ru->{'username'}); # maybe the user was renamed at the remote site if (!$ru_id_1) { ($ru_id_1, $ljr_userid, $ljr_type) = $dbr->selectrow_array( "SELECT ljr_remote_renamed.ru_id, ljr_cached_users.remote_userid, ljr_cached_users.remote_type " . "FROM ljr_remote_renamed, ljr_cached_users " . "WHERE ljr_remote_renamed.old_username = ? and ljr_cached_users.remote_serverid = ? " . "and ljr_remote_renamed.ru_id = ljr_cached_users.ru_id", undef, $ru->{'username'}, $ru->{'serverid'}); } if ($ru_id_1) { $ru->{'ru_id'} = $ru_id_1; $ru->{'userid'} = $ljr_userid; $ru->{'type'} = $ljr_type; } else { # TODO: try to get userid from userinfo.bml # and maybe from lj_gate $dbh->do( "INSERT INTO ljr_cached_users (remote_serverid, remote_username, remote_type) VALUES (?, ?, ?)", undef, $ru->{'serverid'}, $ru->{'username'}, $remote_type); return $err->($dbh->errstr) if $dbh->err; $ru->{'ru_id'} = $dbh->{mysql_insertid}; $ru->{'type'} = $remote_type; } } elsif ($ru->{'userid'} && !$ru->{'username'}) { my ($ru_id_2, $ljr_username, $ljr_type) = $dbr->selectrow_array( "SELECT ru_id, remote_username, remote_type FROM ljr_cached_users " . "WHERE remote_serverid=? and remote_userid=?", undef, $ru->{'serverid'}, $ru->{'userid'}); if ($ru_id_2) { $ru->{'ru_id'} = $ru_id_2; $ru->{'username'} = $ljr_username; $ru->{'type'} = $ljr_type; } else { $dbh->do( "INSERT INTO ljr_cached_users (remote_serverid, remote_userid, remote_type) VALUES (?, ?, ?)", undef, $ru->{'serverid'}, $ru->{'userid'}, $remote_type); return $err->($dbh->errstr) if $dbh->err; $ru->{'ru_id'} = $dbh->{mysql_insertid}; $ru->{'type'} = $remote_type; } } elsif (!$ru->{'userid'} && !$ru->{'username'}) { return $err->("no userid and username supplied."); } } return $ru; } sub get_local_commentid { my ($local_userid, $ru_id, $remote_talkid) = @_; my %res = (); my $dbr = LJ::get_db_reader(); return $err->("No database reader available!") unless $dbr; my $jtalkid; my $local_user; if ($local_userid) { $jtalkid = $dbr->selectrow_array( "SELECT local_jtalkid FROM ljr_cached_comments, ljr_remote_comments " . "WHERE ru_id = ? and commentid = ? and ljr_cached_comments.cc_id = ljr_remote_comments.cc_id " . "and local_journalid = ?", undef, $ru_id, $remote_talkid, $local_userid); return $err->("Can't find remote comment (" . $ru_id . ":" . $remote_talkid . ") for local user $local_userid.") unless $jtalkid; } else { ($local_userid, $jtalkid) = $dbr->selectrow_array( "SELECT local_journalid, local_jtalkid FROM ljr_cached_comments, ljr_remote_comments " . "WHERE ru_id = ? and commentid = ? and ljr_cached_comments.cc_id = ljr_remote_comments.cc_id " . "limit 1", undef, $ru_id, $remote_talkid); return $err->("Can't find remote comment (" . $ru_id . ":" . $remote_talkid . ")") unless $local_userid && $jtalkid; $local_user = LJ::load_userid ($local_userid); } $res{"journalid"} = $local_user->{"userid"}; $res{"journalname"} = $local_user->{"user"}; $res{"talkid"} = $jtalkid; return \%res; } # # name: LJR::Distributed::get_local_itemid # des: returns local itemid corresponding to remote entry # returns: $hashref->{itemid} or $hashref->{err} and $hashref->{errtext} # args: local_user, remote_server, remote_journal, remote_itemid # des-local_user: user object of journal into which we're importing # des-remote_server: remote serverid # des-remote_journal: remote journalid # des-remote_itemid: remote jitemid # sub get_local_itemid { my ($local_user, $ru_id, $remote_itemid, $type) = @_; my %res = (); my $dbr = LJ::get_db_reader(); return $err->("No database reader available!") unless $dbr; $type = "I" unless $type; my $jitemid; my $item; if ($local_user) { # find first jitemid $jitemid = $dbr->selectrow_array( "SELECT local_jitemid FROM ljr_remote_entries " . "WHERE local_journalid=? and sync_type=? and ru_id=? and remote_jitemid=?", undef, $local_user->{"userid"}, $type, $ru_id, $remote_itemid); } else { my $tid; ($tid, $jitemid) = $dbr->selectrow_array( "SELECT local_journalid, local_jitemid FROM ljr_remote_entries " . "WHERE ru_id=? and sync_type=? and remote_jitemid=? limit 1", undef, $ru_id, $type, $remote_itemid); $local_user = LJ::load_userid ($tid); return $err->("Error loading user $tid") unless $local_user; } # check that local entry is still there if ($jitemid) { $item = LJ::get_log2_row($local_user, $jitemid); # if it's not there then break association if (!$item) { $jitemid = 0; my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; $dbh->do( "DELETE FROM ljr_remote_entries WHERE local_journalid=? and sync_type=? and ru_id=? and remote_jitemid=?", undef, $local_user->{"userid"}, $type, $ru_id, $remote_itemid); return $err->($dbh->errstr) if $dbh->err; } } else { $jitemid = 0; } $res{"journalid"} = $local_user->{"userid"}; $res{"journalname"} = $local_user->{"user"}; $res{"itemid"} = $jitemid; $res{"item"} = $item; return \%res; } # # name: LJR::Distributed::store_remote_itemid # des: associates remote entry with local entry # returns: 1 or $hashref->{err} and $hashref->{errtext} # args: local_user, remote_server, remote_journal, remote_itemid # des-local_user: user object of journal into which we're importing # des-local_jitemd: local journal entry id # des-remote_server: remote serverid # des-remote_journal: remote journalid # des-remote_itemid: remote jitemid # sub store_remote_itemid { my ($local_user, $local_jitemid, $ru_id, $remote_itemid, $remote_htmlid, $type) = @_; my %res = (); my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; $type = "I" unless $type; $dbh->do("INSERT INTO ljr_remote_entries VALUES (?,?,?,?,?,?)", undef, $local_user->{"userid"}, $local_jitemid, $ru_id, $remote_itemid, $remote_htmlid, $type); return $err->($dbh->errstr) if $dbh->err; return \%res; } sub remove_remote_itemid { my ($local_user, $local_jitemid, $ru_id, $remote_itemid, $type) = @_; my %res = (); my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; $dbh->do("delete from ljr_remote_entries where local_journalid=? and local_jitemid=? and ru_id=? and remote_jitemid=? and sync_type=?", undef, $local_user->{"userid"}, $local_jitemid, $ru_id, $remote_itemid, $type); return $err->($dbh->errstr) if $dbh->err; return \%res; } # # get remote item id # type: I for imported items, E for exported (gated) items # sub get_remote_itemid { my ($local_journalid, $local_jitemid, $type) = @_; my $res = {}; my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; $type = "I" unless $type; my ($ru_id, $ritemid, $rhtmlid) = $dbr->selectrow_array( "select ru_id, remote_jitemid, remote_htmlid from ljr_remote_entries " . "WHERE local_journalid=? and local_jitemid=? and sync_type=?", undef, $local_journalid, $local_jitemid, $type); if ($ru_id) { $res->{"ru_id"} = $ru_id; $res->{"ritemid"} = $ritemid; $res->{"rhtmlid"} = $rhtmlid; $res = LJR::Distributed::get_cached_user($res); return undef unless $res->{"username"}; $res = LJR::Distributed::get_remote_server_byid($res); return undef unless $res->{"servername"}; $res->{"original_entry"} = $res->{"servername"} . "/users/" . $res->{"username"} . "/" . $res->{"rhtmlid"} . ".html"; return $res; } else { return undef; } } sub sign_imported_entry { my ($journalid, $entryid, $event) = @_; my $ru = LJR::Distributed::get_remote_itemid ($journalid, $entryid); if ($ru && $ru->{"original_entry"}) { $$event .= "\n\n" . "Imported event " . "Original" . ""; } } sub sign_exported_rss_entry { my ($u, $jitemid, $anum, $event) = @_; my $item_url = LJ::item_link($u, $jitemid, $anum); my $dbr = LJ::get_db_reader(); my ($font, $color, $ljr_es_lastmod) = $dbr->selectrow_array( "SELECT font_name, font_color FROM ljr_export_settings WHERE user=?", undef, $u->{'user'}); $font = "gdLargeFont" unless $font; $color = "blue" unless $color; my $img = LJR::GD::generate_number(0, $font, $color, " "); my $padded_width = $img->width; my $padded_height = $img->height; #my $replycounturl = $LJ::SITEROOT . "/comments/" . $jitemid . "/" . $u->{'userid'} ; my $ditemid = $jitemid * 256 + $anum; my $replycounturl = $LJ::SITEROOT . "/numreplies/" . $u->{'user'} . "/" . $ditemid ; my $talklink = "

" . "" . "\"number" . " Comments" . "
"; $$event .= $talklink; } sub sign_exported_gate_entry { my ($u, $jitemid, $anum, $event) = @_; my $item_url = LJ::item_link($u, $jitemid, $anum); my $dbr = LJ::get_db_reader(); my ($font, $color, $ljr_es_lastmod) = $dbr->selectrow_array( "SELECT font_name, font_color FROM ljr_export_settings WHERE user=?", undef, $u->{'user'}); $font = "gdLargeFont" unless $font; $color = "blue" unless $color; my $img = LJR::GD::generate_number(0, $font, $color, " "); my $padded_width = $img->width; my $padded_height = $img->height; #my $replycounturl = $LJ::SITEROOT . "/comments/" . $jitemid . "/" . $u->{'userid'} ; my $ditemid = $jitemid * 256 + $anum; my $replycounturl = $LJ::SITEROOT . "/numreplies/" . $u->{'user'} . "/" . $ditemid ; my $talklink = "
" . "(" . "\"number" . " Comments |Comment on this)
"; $$event .= $talklink; } sub update_export_status { my ($local_user, $mode, $status_text) = @_; my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; my $u = LJ::load_user($local_user, 1); return $err->("Invalid local user: " . $local_user) unless $u; my ($record_exists) = $dbr->selectrow_array( "select count(*) from ljr_export_settings where user=? ", undef, $local_user); return err->("Export is not configured for $local_user") unless $record_exists; if ($mode) { $mode = 1; } else { $mode = 0; } $dbh->do ( "UPDATE ljr_export_settings set enabled=?, update_time=NOW(), last_status=? WHERE user=?", undef, $mode, $status_text, $local_user ); return $err->($dbh->errstr) if $dbh->err; } sub update_export_settings { my ($local_user, $ru_id, $remote_password) = @_; my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; my $dbh = LJ::get_db_writer(); return $err->("Can't get database writer!") unless $dbh; my $u = LJ::load_user($local_user, 1); return $err->("Invalid local user: " . $local_user) unless $u; return $err->("ru_id or remote_password not specified") unless $ru_id && $remote_password; my ($record_exists) = $dbr->selectrow_array( "select count(*) from ljr_export_settings where user=? ", undef, $local_user); if ($record_exists) { $dbh->do( "UPDATE ljr_export_settings SET ru_id=?, remote_password=?, update_time=NOW() WHERE user=?", undef, $ru_id, $remote_password, $local_user ); return $err->($dbh->errstr) if $dbh->err; } else { $dbh->do( "INSERT INTO ljr_export_settings " . "(user, ru_id, remote_password, update_time) " . "VALUES (?,?,?,NOW())", undef, $local_user, $ru_id, $remote_password ); return $err->($dbh->errstr) if $dbh->err; } return LJR::Distributed::update_export_status($local_user, 1, "OK: Updated settings."); } sub is_gated_local { my ($username) = @_; my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; my ($exported) = $dbr->selectrow_array( "select enabled from ljr_export_settings where user=?", undef, $username); return $exported; } sub is_gated_remote { my ($server, $username) = @_; return $err->("Server and username must be specified.") unless $server && $username; my $dbr = LJ::get_db_reader(); return $err->("Can't get database reader!") unless $dbr; my $ru = LJR::Distributed::get_remote_server($server); return $err->($ru->{"errtext"}) if $ru->{"err"}; $ru->{'username'} = $username; $ru = LJR::Distributed::get_cached_user($ru); return $err->($ru->{"errtext"}) if $ru->{"err"}; my ($exported) = $dbr->selectrow_array( "select count(*) from ljr_export_settings where ru_id=?", undef, $ru->{'ru_id'}); return $exported; }