496 lines
16 KiB
Perl
496 lines
16 KiB
Perl
|
|
#!/usr/bin/perl
|
||
|
|
#
|
||
|
|
# DW::Controller::Importer
|
||
|
|
#
|
||
|
|
# Controller for the /tools/importer pages.
|
||
|
|
#
|
||
|
|
# Authors:
|
||
|
|
# Mark Smith <mark@dreamwidth.org>
|
||
|
|
#
|
||
|
|
# Copyright (c) 2012 by Dreamwidth Studios, LLC.
|
||
|
|
#
|
||
|
|
# This program is free software; you may redistribute it and/or modify it under
|
||
|
|
# the same terms as Perl itself. For a copy of the license, please reference
|
||
|
|
# 'perldoc perlartistic' or 'perldoc perlgpl'.
|
||
|
|
#
|
||
|
|
|
||
|
|
package DW::Controller::Importer;
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
use warnings;
|
||
|
|
|
||
|
|
use DW::Routing;
|
||
|
|
use DW::Task::ImportEraser;
|
||
|
|
use DW::Template;
|
||
|
|
use LJ::Hooks;
|
||
|
|
|
||
|
|
DW::Routing->register_string( '/tools/importer/erase', \&erase_handler, app => 1 );
|
||
|
|
DW::Routing->register_string( '/tools/importer', \&importer_handler, app => 1 );
|
||
|
|
|
||
|
|
sub importer_handler {
|
||
|
|
my ( $ok, $rv ) = controller( authas => 1, form_auth => 1 );
|
||
|
|
return $rv unless $ok;
|
||
|
|
|
||
|
|
my $r = $rv->{r};
|
||
|
|
my $POST = $r->post_args;
|
||
|
|
my $u = $rv->{u};
|
||
|
|
my $remote = $rv->{remote};
|
||
|
|
my $authas = $u->user ne $remote->user ? "?authas=" . $u->user : "";
|
||
|
|
my $vars;
|
||
|
|
|
||
|
|
$vars->{remote} = $remote;
|
||
|
|
$vars->{u} = $u;
|
||
|
|
$vars->{allow_comm_imports} = $LJ::ALLOW_COMM_IMPORTS;
|
||
|
|
$vars->{authas} = $authas;
|
||
|
|
$vars->{authas_html} = $rv->{authas_html};
|
||
|
|
my $depth = get_queue();
|
||
|
|
|
||
|
|
$vars->{queue} = join( ', ', map { "$_: " . ( $depth->{ lc $_ } + 0 ) } sort keys %$depth );
|
||
|
|
|
||
|
|
# if these aren't the same users, make sure we're allowed to do a community import
|
||
|
|
unless ( $u->equals($remote) ) {
|
||
|
|
return error_ml('/tools/importer/index.tt.error.cant_import_comm')
|
||
|
|
unless $LJ::ALLOW_COMM_IMPORTS
|
||
|
|
&& ( $u->can_import_comm || $remote->can_import_comm );
|
||
|
|
}
|
||
|
|
|
||
|
|
return error_ml('/tools/importer/index.tt.error.notperson')
|
||
|
|
unless $remote->is_person;
|
||
|
|
|
||
|
|
if ( $r->did_post ) {
|
||
|
|
|
||
|
|
return error_ml('.error.invalidform')
|
||
|
|
unless LJ::check_form_auth( $POST->{lj_form_auth} );
|
||
|
|
|
||
|
|
if ( $POST->{'import'} ) {
|
||
|
|
$vars->{widget} = render_choose_source($vars);
|
||
|
|
}
|
||
|
|
elsif ( $POST->{'choose_source'} ) {
|
||
|
|
my $hn = $POST->{hostname};
|
||
|
|
return error_ml('widget.importchoosesource.error.nohostname') unless $hn;
|
||
|
|
|
||
|
|
# be sure to sanitize the username
|
||
|
|
my $un = LJ::trim( lc $POST->{username} );
|
||
|
|
$un =~ s/-/_/g;
|
||
|
|
|
||
|
|
# be sure to sanitize the usejournal, and require one if they're importing to
|
||
|
|
# a community
|
||
|
|
my $uj;
|
||
|
|
if ( $u->is_community ) {
|
||
|
|
$uj = LJ::trim( lc $POST->{usejournal} );
|
||
|
|
$uj =~ s/-/_/g;
|
||
|
|
return error_ml('/tools/importer/index.tt.error.missing_comm')
|
||
|
|
unless $uj;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $pw = LJ::trim( $POST->{password} );
|
||
|
|
return error_ml('widget.importchoosesource.error.nocredentials') unless $un && $pw;
|
||
|
|
my $error = DW::Logic::Importer->set_import_data_for_user(
|
||
|
|
$u,
|
||
|
|
hostname => $hn,
|
||
|
|
username => $un,
|
||
|
|
password => $pw,
|
||
|
|
usejournal => $uj
|
||
|
|
);
|
||
|
|
return error_ml($error) if $error;
|
||
|
|
$vars->{widget} = render_choose_data($vars);
|
||
|
|
}
|
||
|
|
elsif ( $POST->{'choose_data'} ) {
|
||
|
|
my $has_items = 0;
|
||
|
|
foreach my $key ( keys %$POST ) {
|
||
|
|
if ( $key =~ /^lj_/ && $POST->{$key} ) {
|
||
|
|
$has_items = 1;
|
||
|
|
last;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return error_ml('widget.importchoosedata.error.noitemsselected')
|
||
|
|
unless $has_items;
|
||
|
|
|
||
|
|
# if we're doing the suboption, turn on the parent option
|
||
|
|
$POST->{lj_entries} = 1
|
||
|
|
if $POST->{lj_entries_remap_icon};
|
||
|
|
|
||
|
|
# if comments are on, turn entries on
|
||
|
|
$POST->{lj_entries} = 1
|
||
|
|
if $POST->{lj_comments};
|
||
|
|
|
||
|
|
# okay, this is kinda hacky but turn on the right things so we can do
|
||
|
|
# a proper entry import...
|
||
|
|
if ( $POST->{lj_entries} ) {
|
||
|
|
$POST->{lj_tags} = 1;
|
||
|
|
$POST->{lj_friendgroups} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# if friends are on, turn on groups
|
||
|
|
$POST->{lj_friendgroups} = 1
|
||
|
|
if $POST->{lj_friends};
|
||
|
|
|
||
|
|
# everybody needs a verifier
|
||
|
|
$POST->{lj_verify} = 1;
|
||
|
|
|
||
|
|
# and finally, make sure modes that make no sense for communities are off
|
||
|
|
if ( $u->is_community ) {
|
||
|
|
$POST->{lj_friends} = 0;
|
||
|
|
$POST->{lj_friendgroups} = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$vars->{widget} = render_confirm( $vars, $POST );
|
||
|
|
|
||
|
|
}
|
||
|
|
elsif ( $POST->{'confirm'} ) {
|
||
|
|
|
||
|
|
# default job status
|
||
|
|
my @jobs = (
|
||
|
|
[ 'lj_verify', 'ready' ],
|
||
|
|
[ 'lj_userpics', 'init' ],
|
||
|
|
[ 'lj_bio', 'init' ],
|
||
|
|
[ 'lj_tags', 'init' ],
|
||
|
|
[ 'lj_friendgroups', 'init' ],
|
||
|
|
[ 'lj_friends', 'init' ],
|
||
|
|
[ 'lj_entries', 'init' ],
|
||
|
|
[ 'lj_comments', 'init' ],
|
||
|
|
);
|
||
|
|
|
||
|
|
my %suboptions = ( lj_entries => ['lj_entries_remap_icon'], );
|
||
|
|
|
||
|
|
# get import_data_id for the user
|
||
|
|
my $imports = DW::Logic::Importer->get_import_data_for_user($u);
|
||
|
|
|
||
|
|
my $id = $imports->[0]->[0];
|
||
|
|
my $error;
|
||
|
|
|
||
|
|
# schedule userpic, bio, and tag imports
|
||
|
|
foreach my $item (@jobs) {
|
||
|
|
next unless $POST->{ $item->[0] };
|
||
|
|
|
||
|
|
my $suboption = $suboptions{ $item->[0] } || [];
|
||
|
|
my %opts;
|
||
|
|
foreach (@$suboption) {
|
||
|
|
$opts{$_} = 1 if $POST->{$_};
|
||
|
|
}
|
||
|
|
$error =
|
||
|
|
DW::Logic::Importer->set_import_items_for_user( $u, item => $item, id => $id );
|
||
|
|
return error_ml($error) if $error;
|
||
|
|
|
||
|
|
$error = DW::Logic::Importer->set_import_data_options_for_user(
|
||
|
|
$u,
|
||
|
|
import_data_id => $id,
|
||
|
|
%opts
|
||
|
|
);
|
||
|
|
return error_ml($error) if $error;
|
||
|
|
}
|
||
|
|
return $r->redirect("$LJ::SITEROOT/tools/importer$authas");
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
if ( scalar keys %{ DW::Logic::Importer->get_import_items_for_user($u) } ) {
|
||
|
|
$vars->{widget} = render_status($vars);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$vars->{widget} = render_choose_source($vars);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
return DW::Template->render_template( 'tools/importer/index.tt', $vars );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub render_choose_data {
|
||
|
|
my $vars = shift;
|
||
|
|
my $options = [
|
||
|
|
{
|
||
|
|
name => 'lj_bio',
|
||
|
|
display_name => LJ::Lang::ml('widget.importstatus.item.lj_bio'),
|
||
|
|
desc => LJ::Lang::ml('widget.importchoosedata.item.lj_bio.desc'),
|
||
|
|
selected => 0,
|
||
|
|
comm_okay => 1,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name => 'lj_friends',
|
||
|
|
display_name => LJ::Lang::ml('widget.importstatus.item.lj_friends'),
|
||
|
|
desc => LJ::Lang::ml('widget.importchoosedata.item.lj_friends.desc'),
|
||
|
|
selected => 0,
|
||
|
|
comm_okay => 0,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name => 'lj_friendgroups',
|
||
|
|
display_name => LJ::Lang::ml('widget.importstatus.item.lj_friendgroups'),
|
||
|
|
desc => LJ::Lang::ml(
|
||
|
|
'widget.importchoosedata.item.lj_friendgroups.desc',
|
||
|
|
{ sitename => $LJ::SITENAMESHORT }
|
||
|
|
),
|
||
|
|
selected => 0,
|
||
|
|
comm_okay => 0,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name => 'lj_entries',
|
||
|
|
display_name => LJ::Lang::ml('widget.importstatus.item.lj_entries'),
|
||
|
|
desc => LJ::Lang::ml('widget.importchoosedata.item.lj_entries.desc'),
|
||
|
|
selected => 0,
|
||
|
|
comm_okay => 1,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name => 'lj_comments',
|
||
|
|
display_name => LJ::Lang::ml('widget.importstatus.item.lj_comments'),
|
||
|
|
desc => LJ::Lang::ml('widget.importchoosedata.item.lj_comments.desc'),
|
||
|
|
selected => 0,
|
||
|
|
comm_okay => 1,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name => 'lj_tags',
|
||
|
|
display_name => LJ::Lang::ml('widget.importstatus.item.lj_tags'),
|
||
|
|
desc => LJ::Lang::ml('widget.importchoosedata.item.lj_tags.desc'),
|
||
|
|
selected => 0,
|
||
|
|
comm_okay => 1,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name => 'lj_userpics',
|
||
|
|
display_name => LJ::Lang::ml('widget.importstatus.item.lj_userpics'),
|
||
|
|
desc => LJ::Lang::ml(
|
||
|
|
'widget.importchoosedata.item.lj_userpics.desc',
|
||
|
|
{ sitename => $LJ::SITENAMESHORT }
|
||
|
|
),
|
||
|
|
selected => 0,
|
||
|
|
comm_okay => 1,
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
my $fixup_options = [
|
||
|
|
{
|
||
|
|
name => 'lj_entries_remap_icon',
|
||
|
|
display_name => LJ::Lang::ml('widget.importstatus.item.lj_entries_remap_icon'),
|
||
|
|
desc => LJ::Lang::ml('widget.importchoosedata.item.lj_entries_remap_icon.desc'),
|
||
|
|
selected => 0,
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
$vars->{options} = $options;
|
||
|
|
$vars->{fixup_options} = $fixup_options;
|
||
|
|
|
||
|
|
return DW::Template->template_string( 'tools/importer/choose_data.tt', $vars );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub render_choose_source {
|
||
|
|
my $vars = shift;
|
||
|
|
|
||
|
|
return error_ml('widget.importchoosesource.disabled1')
|
||
|
|
unless LJ::is_enabled('importing');
|
||
|
|
|
||
|
|
my @services;
|
||
|
|
|
||
|
|
for my $service (
|
||
|
|
(
|
||
|
|
{
|
||
|
|
name => 'livejournal',
|
||
|
|
url => 'livejournal.com',
|
||
|
|
display_name => 'LiveJournal',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name => 'insanejournal',
|
||
|
|
url => 'insanejournal.com',
|
||
|
|
display_name => 'InsaneJournal',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name => 'dreamwidth',
|
||
|
|
url => 'dreamwidth.org',
|
||
|
|
display_name => 'Dreamwidth',
|
||
|
|
},
|
||
|
|
)
|
||
|
|
)
|
||
|
|
{
|
||
|
|
# only dev servers can import from Dreamwidth for testing
|
||
|
|
next if ( $service->{name} eq 'dreamwidth' ) && !$LJ::IS_DEV_SERVER;
|
||
|
|
push @services,
|
||
|
|
$service
|
||
|
|
if LJ::is_enabled( "external_sites",
|
||
|
|
{ sitename => $service->{display_name}, domain => $service->{url} } );
|
||
|
|
}
|
||
|
|
$vars->{services} = \@services;
|
||
|
|
|
||
|
|
return DW::Template->template_string( 'tools/importer/choose_source.tt', $vars );
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
sub render_confirm {
|
||
|
|
my ( $vars, $opts ) = @_;
|
||
|
|
|
||
|
|
my @items_fields;
|
||
|
|
my @items_display;
|
||
|
|
foreach my $item ( keys %$opts ) {
|
||
|
|
next unless $item =~ /^lj_/ && $opts->{$item};
|
||
|
|
push @items_fields, $item unless $item eq 'lj_form_auth';
|
||
|
|
push @items_display, LJ::Lang::ml("widget.importstatus.item.$item")
|
||
|
|
unless ( $item eq 'lj_verify' )
|
||
|
|
or ( $item eq 'lj_form_auth' );
|
||
|
|
}
|
||
|
|
my $imports = DW::Logic::Importer->get_import_data_for_user( $vars->{u} );
|
||
|
|
|
||
|
|
$vars->{items_fields} = \@items_fields;
|
||
|
|
$vars->{items_display} = \@items_display;
|
||
|
|
$vars->{imports} = $imports;
|
||
|
|
|
||
|
|
return DW::Template->template_string( 'tools/importer/confirm.tt', $vars );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub render_status {
|
||
|
|
my $vars = shift;
|
||
|
|
my $items = DW::Logic::Importer->get_import_items_for_user( $vars->{u} );
|
||
|
|
my $item_to_funcname = {
|
||
|
|
lj_bio => 'DW::Worker::ContentImporter::LiveJournal::Bio',
|
||
|
|
lj_tags => 'DW::Worker::ContentImporter::LiveJournal::Tags',
|
||
|
|
lj_entries => 'DW::Worker::ContentImporter::LiveJournal::Entries',
|
||
|
|
lj_comments => 'DW::Worker::ContentImporter::LiveJournal::Comments',
|
||
|
|
lj_userpics => 'DW::Worker::ContentImporter::LiveJournal::Userpics',
|
||
|
|
lj_friends => 'DW::Worker::ContentImporter::LiveJournal::Friends',
|
||
|
|
lj_friendgroups => 'DW::Worker::ContentImporter::LiveJournal::FriendGroups',
|
||
|
|
lj_verify => 'DW::Worker::ContentImporter::LiveJournal::Verify',
|
||
|
|
};
|
||
|
|
|
||
|
|
my $dbr;
|
||
|
|
my $funcmap;
|
||
|
|
my $dupect = 0;
|
||
|
|
my $color = {
|
||
|
|
init => '#333',
|
||
|
|
ready => '#33a',
|
||
|
|
queued => '#3a3',
|
||
|
|
failed => '#a33',
|
||
|
|
succeeded => '#0f0',
|
||
|
|
aborted => '#f00'
|
||
|
|
};
|
||
|
|
foreach my $importid ( sort { $b <=> $a } keys %$items ) {
|
||
|
|
my $import_item = $items->{$importid};
|
||
|
|
foreach my $item ( sort keys %{ $import_item->{items} } ) {
|
||
|
|
my $i = $import_item->{items}->{$item};
|
||
|
|
$i->{color} = $color->{ $i->{status} };
|
||
|
|
$i->{ago} = $i->{last_touch} ? LJ::diff_ago_text( $i->{last_touch} ) : "";
|
||
|
|
if ( $i->{status} eq 'init' ) {
|
||
|
|
$i->{status_txt} = LJ::Lang::ml("widget.importstatus.status.$i->{status}.$item");
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$i->{status_txt} = LJ::Lang::ml("widget.importstatus.status.$i->{status}");
|
||
|
|
|
||
|
|
if ( $i->{status} eq "aborted" ) {
|
||
|
|
unless ($dbr) {
|
||
|
|
|
||
|
|
# do manual connection
|
||
|
|
my $db = $LJ::THESCHWARTZ_DBS[0];
|
||
|
|
$dbr = DBI->connect( $db->{dsn}, $db->{user}, $db->{pass} );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($dbr) {
|
||
|
|
|
||
|
|
# get the ids for the function map
|
||
|
|
$funcmap ||=
|
||
|
|
$dbr->selectall_hashref( 'SELECT funcid, funcname FROM funcmap',
|
||
|
|
'funcname' );
|
||
|
|
|
||
|
|
$dupect = $dbr->selectrow_array(
|
||
|
|
q{SELECT COUNT(*) from job
|
||
|
|
WHERE funcid = ?
|
||
|
|
AND uniqkey = ? },
|
||
|
|
undef, $funcmap->{ $item_to_funcname->{$item} }->{funcid},
|
||
|
|
join( "-", ( $item, $vars->{u}->id ) )
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$i->{dupe} =
|
||
|
|
$dupect ? " " . LJ::Lang::ml("widget.importstatus.processingprevious") : "";
|
||
|
|
}
|
||
|
|
$vars->{items} = $items;
|
||
|
|
$vars->{time_ago} = \&LJ::diff_ago_text;
|
||
|
|
}
|
||
|
|
return DW::Template->template_string( 'tools/importer/status.tt', $vars );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_queue {
|
||
|
|
my $depth = LJ::MemCache::get('importer_queue_depth');
|
||
|
|
unless ($depth) {
|
||
|
|
|
||
|
|
# FIXME: don't make this slam the db with people asking the same question, use a lock
|
||
|
|
# FIXME: we don't have ddlockd, maybe we should
|
||
|
|
|
||
|
|
# do manual connection
|
||
|
|
my $db = $LJ::THESCHWARTZ_DBS[0];
|
||
|
|
my $dbr = DBI->connect( $db->{dsn}, $db->{user}, $db->{pass} )
|
||
|
|
or return "Unable to manually connect to TheSchwartz database.";
|
||
|
|
|
||
|
|
# get the ids for the function map
|
||
|
|
my $tmpmap = $dbr->selectall_hashref( 'SELECT funcid, funcname FROM funcmap', 'funcname' );
|
||
|
|
|
||
|
|
# get the counts of jobs in queue (active or not)
|
||
|
|
my %cts;
|
||
|
|
foreach my $map ( keys %$tmpmap ) {
|
||
|
|
next unless $map =~ /^DW::Worker::ContentImporter::LiveJournal::/;
|
||
|
|
|
||
|
|
my $ct = $dbr->selectrow_array(
|
||
|
|
q{SELECT COUNT(*) FROM job
|
||
|
|
WHERE funcid = ?
|
||
|
|
AND run_after < UNIX_TIMESTAMP()},
|
||
|
|
undef, $tmpmap->{$map}->{funcid}
|
||
|
|
) + 0;
|
||
|
|
|
||
|
|
$map =~ s/^.+::(\w+)$/$1/;
|
||
|
|
$cts{ lc $map } = $ct;
|
||
|
|
}
|
||
|
|
|
||
|
|
LJ::MemCache::set( 'importer_queue_depth', \%cts, 300 );
|
||
|
|
$depth = \%cts;
|
||
|
|
}
|
||
|
|
return $depth;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub erase_handler {
|
||
|
|
my ( $ok, $rv ) = controller( authas => 1 );
|
||
|
|
return $rv unless $ok;
|
||
|
|
|
||
|
|
my $r = DW::Request->get;
|
||
|
|
unless ( $r->did_post ) {
|
||
|
|
|
||
|
|
# No post, return form.
|
||
|
|
return DW::Template->render_template(
|
||
|
|
'tools/importer/erase.tt',
|
||
|
|
{
|
||
|
|
authas_html => $rv->{authas_html},
|
||
|
|
u => $rv->{u},
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
my $args = $r->post_args;
|
||
|
|
die "Invalid form auth.\n"
|
||
|
|
unless LJ::check_form_auth( $args->{lj_form_auth} );
|
||
|
|
|
||
|
|
unless ( $args->{confirm} eq 'DELETE' ) {
|
||
|
|
return DW::Template->render_template(
|
||
|
|
'tools/importer/erase.tt',
|
||
|
|
{
|
||
|
|
notconfirmed => 1,
|
||
|
|
authas_html => $rv->{authas_html},
|
||
|
|
u => $rv->{u},
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
# Confirmed, let's schedule.
|
||
|
|
DW::TaskQueue->dispatch(
|
||
|
|
DW::Task::ImportEraser->new(
|
||
|
|
{
|
||
|
|
userid => $rv->{u}->userid
|
||
|
|
}
|
||
|
|
)
|
||
|
|
) or die "Failed to insert eraser job.\n";
|
||
|
|
|
||
|
|
return DW::Template->render_template(
|
||
|
|
'tools/importer/erase.tt',
|
||
|
|
{
|
||
|
|
u => $rv->{u},
|
||
|
|
confirmed => 1,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
1;
|