[dw-free] Userpic keyword renaming
[commit: http://hg.dwscoalition.org/dw-free/rev/be246bd19221]
http://bugs.dwscoalition.org/show_bug.cgi?id=53
Add ability to actually rename icon keywords. When you edit the keywords on
an icon, you can click a box to do a rename on those keywords, and comments
and posts you've made with the old keywords will be updated to use the new
ones.
Patch by
allen.
Files modified:
http://bugs.dwscoalition.org/show_bug.cgi?id=53
Add ability to actually rename icon keywords. When you edit the keywords on
an icon, you can click a box to do a rename on those keywords, and comments
and posts you've made with the old keywords will be updated to use the new
ones.
Patch by
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Files modified:
- bin/worker/userpic-rename
- cgi-bin/DW/Worker/UserpicRenameWorker.pm
- cgi-bin/LJ/Userpic.pm
- htdocs/editpics.bml
- htdocs/editpics.bml.text
- htdocs/stc/editpics.css
-------------------------------------------------------------------------------- diff -r e36a761d81b3 -r be246bd19221 bin/worker/userpic-rename --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/worker/userpic-rename Tue Nov 24 20:12:09 2009 +0000 @@ -0,0 +1,27 @@ +#!/usr/bin/perl +# +# bin/worker/userpic-rename +# +# TheSchwartz worker for renaming userpics +# +# Authors: +# Allen Petersen <allen@suberic.net> +# +# Copyright (c) 2009 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'. + +use strict; +use warnings; + +use lib "$ENV{LJHOME}/cgi-bin"; +use LJ::Worker::TheSchwartz; +use DW::Worker::UserpicRenameWorker; + +foreach my $capability (DW::Worker::UserpicRenameWorker->schwartz_capabilities) { + schwartz_decl( $capability ); +} + +schwartz_work(); # Never returns. diff -r e36a761d81b3 -r be246bd19221 cgi-bin/DW/Worker/UserpicRenameWorker.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cgi-bin/DW/Worker/UserpicRenameWorker.pm Tue Nov 24 20:12:09 2009 +0000 @@ -0,0 +1,144 @@ +#!/usr/bin/perl +# +# DW::Worker::UserpicRenameWorker +# +# TheSchwartz worker module for renaming userpics. Called with +# LJ::theschwartz()->insert('DW::Worker::UserpicRenameWorker', { +# 'uid' => $u->userid, 'keywordmap' => Storable::nfreeze(\%keywordmap); +# +# Authors: +# Allen Petersen <allen@suberic.net> +# +# Copyright (c) 2009 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'. + +use strict; +use warnings; +use lib "$LJ::HOME/cgi-bin"; + +package DW::Worker::UserpicRenameWorker; +use base 'TheSchwartz::Worker'; + +sub schwartz_capabilities { return ('DW::Worker::UserpicRenameWorker'); } + +sub keep_exit_status_for { 86400 } # 24 hours + +my $logpropid; +my $talkpropid; + +sub work { + my ($class, $job) = @_; + + my $arg = $job->arg; + + my ($uid, $keywordmapstring) = map { delete $arg->{$_} } qw( uid keywordmap ); + + return $job->permanent_failure("Unknown keys: " . join(", ", keys %$arg)) + if keys %$arg; + return $job->permanent_failure("Missing argument") + unless defined $uid && defined $keywordmapstring; + + my $keywordmap = eval { Storable::thaw($keywordmapstring) } or return $job->failed("Failed to load keywordmap from arg '$keywordmapstring': " . $@); + + # get the user from the uid + my $u = LJ::want_user($uid) or return $job->failed("Unable to load user with uid $uid"); + + # only get the propids once; they're not going to change + unless ($logpropid && $talkpropid) { + $logpropid = LJ::get_prop( log => 'picture_keyword' )->{id}; + $talkpropid = LJ::get_prop( talk => 'picture_keyword' )->{id}; + } + + # only update 1000 rows at a time. + my $LIMIT = 1000; + + # now we go through each cluster and update the logprop2 and talkprop2 + # tables with the new values. + foreach my $cluster_id (@LJ::CLUSTERS) { + my $dbcm = LJ::get_cluster_master($cluster_id); + + foreach my $kw (keys %$keywordmap) { + # find entries for clearing cache + my $matches = $dbcm->selectall_arrayref(q{ + SELECT log2.journalid AS journalid, + log2.jitemid AS jitemid + FROM logprop2 + INNER JOIN log2 + ON ( logprop2.journalid = log2.journalid + AND logprop2.jitemid = log2.jitemid ) + WHERE posterid = ? + AND propid=? + AND value = ? + }, undef, $u->id, $logpropid, $kw); + + # update entries + my $updateresults = $LIMIT; + while ($updateresults == $LIMIT) { + $updateresults = $dbcm->do( q{ + UPDATE logprop2 + SET value = ? + WHERE propid=? + AND value = ? + AND EXISTS ( + SELECT posterid + FROM log2 + WHERE log2.journalid = logprop2.journalid + AND log2.jitemid = logprop2.jitemid + AND log2.posterid = ? ) + LIMIT ? + }, undef, $keywordmap->{$kw}, $logpropid, $kw, $u->id, $LIMIT); + return $job->permanent_failure($dbcm->errstr) if $dbcm->err; + } + + # clear cache + foreach my $match (@$matches) { + LJ::MemCache::delete([ $match->[0], "logprop:" . $match->[0]. ":" . $match->[1] ]); + } + + # update comments + # find comments for clearing cache + $matches = $dbcm->selectall_arrayref( q{ + SELECT talkprop2.journalid AS journalid, + talkprop2.jtalkid AS jtalkid + FROM talkprop2 + INNER JOIN talk2 + ON ( talkprop2.journalid = talk2.journalid + AND talkprop2.jtalkid = talk2.jtalkid ) + WHERE posterid = ? + AND tpropid=? + AND value = ? + }, undef, $u->id, $talkpropid, $kw); + + # update coments + $updateresults = $LIMIT; + while ($updateresults == $LIMIT) { + $updateresults = $dbcm->do( q{ + UPDATE talkprop2 + SET value = ? + WHERE tpropid=? + AND value = ? + AND EXISTS ( + SELECT posterid + FROM talk2 + WHERE talk2.journalid = talkprop2.journalid + AND talk2.jtalkid = talkprop2.jtalkid + AND talk2.posterid = ? ) + LIMIT ? + }, undef, $keywordmap->{$kw}, $talkpropid, $kw, $u->id, $LIMIT); + return $job->permanent_failure($dbcm->errstr) if $dbcm->err; + } + + # clear cache + foreach my $match (@$matches) { + LJ::MemCache::delete([ $match->[0], "talkprop:" . $match->[0]. ":" . $match->[1] ]); + } + } + } + + $job->completed; +} + +1; diff -r e36a761d81b3 -r be246bd19221 cgi-bin/LJ/Userpic.pm --- a/cgi-bin/LJ/Userpic.pm Tue Nov 24 20:03:33 2009 +0000 +++ b/cgi-bin/LJ/Userpic.pm Tue Nov 24 20:12:09 2009 +0000 @@ -4,6 +4,7 @@ use Digest::MD5; use Digest::MD5; use LJ::Event::NewUserpic; use LJ::Constants; +use Storable; ## ## Potential properties of an LJ::Userpic object @@ -919,6 +920,97 @@ sub set_keywords { return 1; } + +# instance method: takes two strings of comma-separated keywords, the first +# being the new set of keywords, the second being the old set of keywords. +# +# the new keywords must be the same number as the old keywords; that is, +# if the userpic has three keywords and you want to rename them, you must +# rename them to three keywords (some can match). otherwise there would be +# some ambiguity about which old keywords should match up with the new +# keywords. if the number of keywords don't match, then an error is thrown +# and no changes are made to the keywords for this userpic. +# +# all new keywords must not currently be in use; you can't rename a keyword +# to a keyword currently mapped to another (or the same) userpic. this will +# result in an error and no changes made to these keywords. +# +# the setting of new keywords is done by Userpic->set_keywords; the remapping +# of existing pics is done asynchonously via TheSchwartz using the +# DW::Worker::UserpicRenameWorker. +sub set_and_rename_keywords { + my $self = shift; + my $new_keyword_string = $_[0]; + my $orig_keyword_string = $_[1]; + + my $sclient = LJ::theschwartz(); + + # error out if TheSchwartz isn't available. + LJ::errobj("Userpic::RenameKeywords", + origkw => $orig_keyword_string, + newkw => $new_keyword_string)->throw unless $sclient; + + my @keywords = split(',', $new_keyword_string); + my @orig_keywords = split(',', $orig_keyword_string); + + # don't allow renames involving no-keyword (pic#0001) values + if ( grep ( /^\s*pic\#\d+\s*$/, @orig_keywords ) || grep ( /^\s*pic\#\d+\s*$/, @keywords )) { + LJ::errobj("Userpic::RenameBlankKeywords", + origkw => $orig_keyword_string, + newkw => $new_keyword_string)->throw; + } + + # compare sizes + if (scalar @keywords ne scalar @orig_keywords) { + LJ::errobj("Userpic::MismatchRenameKeywords", + origkw => $orig_keyword_string, + newkw => $new_keyword_string)->throw; + } + + #interleave these into a map, excluding duplicates + my %keywordmap; + foreach my $newkw (@keywords) { + my $origkw = shift(@orig_keywords); + # clear whitespace + $newkw =~ s/^\s+//; + $newkw =~ s/\s+$//; + $origkw =~ s/^\s+//; + $origkw =~ s/\s+$//; + + $keywordmap{$origkw} = $newkw if $origkw ne $newkw; + } + + # make sure there is at least one change. + if (keys(%keywordmap)) { + + #make sure that none of the target keywords already exist. + my $u = $self->owner; + foreach my $kw (keys %keywordmap) { + if (LJ::get_picid_from_keyword($u, $keywordmap{$kw}, -1) != -1) { + LJ::errobj("Userpic::RenameKeywordExisting", + keyword => $keywordmap{$kw})->throw; + } + } + + # set the keywords for this userpic to the new set of keywords + $self->set_keywords(@keywords); + + # send to TheSchwartz to do the actual renaming + my $job = TheSchwartz::Job->new_from_array( + 'DW::Worker::UserpicRenameWorker', { + 'uid' => $u->userid, + 'keywordmap' => Storable::nfreeze(\%keywordmap) } ); + + unless ($job && $sclient->insert($job)) { + LJ::errobj("Userpic::RenameKeywords", + origkw => $orig_keyword_string, + newkw => $new_keyword_string)->throw; + } + } + + return 1; +} + sub set_fullurl { my ($self, $url) = @_; my $u = $self->owner; @@ -988,6 +1080,45 @@ sub as_html { { 'filetype' => $self->{'type'} }); } +package LJ::Error::Userpic::MismatchRenameKeywords; +sub user_caused { 1 } +sub fields { qw(origkw newkw); } +sub as_html { + my $self = shift; + return BML::ml("/editpics.bml.error.rename.mismatchedlength", + { 'origkw' => $self->{'origkw'}, + 'newkw' => $self->{'newkw'} }); +} + +package LJ::Error::Userpic::RenameBlankKeywords; +sub user_caused { 1 } +sub fields { qw(origkw newkw); } +sub as_html { + my $self = shift; + return BML::ml("/editpics.bml.error.rename.blankkw", + { 'origkw' => $self->{'origkw'}, + 'newkw' => $self->{'newkw'} }); +} + +package LJ::Error::Userpic::RenameKeywordExisting; +sub user_caused { 1 } +sub fields { qw(keyword); } +sub as_html { + my $self = shift; + return BML::ml("/editpics.bml.error.rename.keywordexists", + { 'keyword' => $self->{'keyword'} }); +} + +package LJ::Error::Userpic::RenameKeywords; +sub user_caused { 0 } +sub fields { qw(origkw newkw); } +sub as_html { + my $self = shift; + return BML::ml("/editpics.bml.error.rename.keywords", + { 'origkw' => $self->{'origkw'}, + 'newkw' => $self->{'newkw'} }); +} + package LJ::Error::Userpic::DeleteFailed; sub user_caused { 0 } diff -r e36a761d81b3 -r be246bd19221 htdocs/editpics.bml --- a/htdocs/editpics.bml Tue Nov 24 20:03:33 2009 +0000 +++ b/htdocs/editpics.bml Tue Nov 24 20:12:09 2009 +0000 @@ -78,6 +78,9 @@ use strict; ### save changes to existing pics if ($POST{'action:save'}) { my $refresh = update_userpics(\@userpics, $u, $err); + + # push all the errors to info + push(@info, @errors); # reload the pictures to account for deleted @userpics = LJ::Userpic->load_user_userpics($u) if $refresh; @@ -474,6 +477,8 @@ use strict; $body .= "</div>"; $body .= "<div id='list_userpics' style='width: 98%; float: left;'>"; + my $display_rename = LJ::theschwartz() ? 1 : 0; + foreach my $pic (@userpics) { my $pid = $pic->id; @@ -489,9 +494,20 @@ use strict; $body .= "<label class='left' for='kw_$pid'>$ML{'.label.keywords'}</label>\n "; $body .= LJ::html_text({'name' => "kw_$pid", 'class' => "text", 'id' => "kw_$pid", 'value' => $keywords, - 'disabled' => $pic->inactive }) . "\n"; + 'disabled' => $pic->inactive, + 'onfocus' => $display_rename ? "\$(\'rename_div_$pid\').style.display = \'block\';" : "" + }) . "\n"; $body .= LJ::html_hidden({ 'name' => "kw_orig_$pid", 'value' => $keywords }) . "\n"; + if ($display_rename) { + $body .= "<div id='rename_div_$pid' class='userpic_rename pkg'>\n"; + $body .= "<script type='text/javascript'>document.getElementById('rename_div_$pid').style.display = 'none';</script>\n"; + $body .= LJ::html_check({ 'type' => 'checkbox', 'name' => "rename_keyword_$pid", 'class' => "checkbox", + 'id' => "rename_keyword_$pid", 'value' => 1, + 'disabled' => $LJ::DISABLE_MEDIA_UPLOADS }); + $body .= "<label for='rename_keyword_$pid'>$ML{'.label.rename'}</label>"; + $body .= "</div>\n"; + } $body .= "</div>\n"; $body .= "<div class='userpic_comments pkg'>\n"; @@ -612,10 +628,15 @@ sub update_userpics if ($POST{"kw_$picid"} ne $POST{"kw_orig_$picid"}) { my $kws = $POST{"kw_$picid"}; - eval { - $up->set_keywords($kws); - } or push @errors, $@; - + if ($POST{"rename_keyword_$picid"}) { + eval { + $up->set_and_rename_keywords($kws, $POST{"kw_orig_$picid"}); + } or push @errors, $@->as_html; + } else { + eval { + $up->set_keywords($kws); + } or push @errors, $@; + } } eval { diff -r e36a761d81b3 -r be246bd19221 htdocs/editpics.bml.text --- a/htdocs/editpics.bml.text Tue Nov 24 20:03:33 2009 +0000 +++ b/htdocs/editpics.bml.text Tue Nov 24 20:12:09 2009 +0000 @@ -39,6 +39,14 @@ .error.nourl=You must enter the URL of an image .error.nomediauploads.delete=Unable to delete userpics at this time. Media modification is currently disabled for the site. + +.error.rename.blankkw=Error renaming '[[origkw]]' to '[[newkw]]': renaming of blank (pic#) keywords not allowed. + +.error.rename.keywordexists=Cannot rename to '[[keyword]]': keyword already exists. + +.error.rename.keywords=An error occurred trying to rename '[[origkw]]' to '[[newkw]]'; entries and comments with the old keywords may not have changed to use the new keywords. + +.error.rename.mismatchedlength=Error renaming '[[origkw]]' to '[[newkw]]': must rename to the same number of keywords. .error.toolarge.separately=Image is too large; please submit it separately to resize. @@ -89,6 +97,8 @@ .label.keywords.desc=Comma-separated list of terms you'll use to select this picture. +.label.rename=Rename Keywords + .makedefault=Make this your <u>d</u>efault picture .makedefault.key|notes=Enter here a <b>lower-case</b> version of the letter you underlined in ".makedefault". This is the shortcut key for the makedefault option. diff -r e36a761d81b3 -r be246bd19221 htdocs/stc/editpics.css --- a/htdocs/stc/editpics.css Tue Nov 24 20:03:33 2009 +0000 +++ b/htdocs/stc/editpics.css Tue Nov 24 20:12:09 2009 +0000 @@ -88,6 +88,12 @@ hr { .userpic_defaultdelete { width: 200px; } +.userpic_rename { + width: 200px; + margin-left: 6.5em; + margin-bottom: 4px; + margin-top: 4px; +} * html .userpic_keywords, * html .userpic_comments, * html .userpic_descriptions { --------------------------------------------------------------------------------