mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)
Mark Smith ([staff profile] mark) wrote in [site community profile] changelog2009-11-24 08:12 pm

[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 [personal profile] allen.

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 {
--------------------------------------------------------------------------------

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org