diff --git a/app/models/note.rb b/app/models/note.rb index 299498ab8..57418f6ea 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -127,6 +127,14 @@ class Note < ActiveRecord::Base User.id_to_name(creator_id) end + def rescale!(x_scale, y_scale) + self.x *= x_scale + self.y *= y_scale + self.width *= x_scale + self.height *= y_scale + save! + end + def update_post if self.changed? if Note.where(:is_active => true, :post_id => post_id).exists? diff --git a/app/models/post_replacement.rb b/app/models/post_replacement.rb index 1cfa6f93a..9cd52a02b 100644 --- a/app/models/post_replacement.rb +++ b/app/models/post_replacement.rb @@ -17,11 +17,6 @@ class PostReplacement < ActiveRecord::Base end def process! - # TODO for posts with notes we need to rescale the notes if the dimensions change. - if post.notes.any? - raise NotImplementedError.new("Replacing images with notes not yet supported.") - end - # TODO for ugoiras we need to replace the frame data. if post.is_ugoira? raise NotImplementedError.new("Replacing ugoira images not yet supported.") @@ -49,9 +44,10 @@ class PostReplacement < ActiveRecord::Base post.file_size = upload.file_size post.source = upload.source post.tag_string = upload.tag_string + rescale_notes - post.comments.create!({creator: User.system, body: post.presenter.comment_replacement_message(creator), do_not_bump_post: true}, without_protection: true) - ModAction.log(post.presenter.modaction_replacement_message) + post.comments.create!({creator: User.system, body: comment_replacement_message, do_not_bump_post: true}, without_protection: true) + ModAction.log(modaction_replacement_message) post.save! end @@ -62,6 +58,15 @@ class PostReplacement < ActiveRecord::Base post.update_iqdb_async end + def rescale_notes + x_scale = post.image_width.to_f / post.image_width_was.to_f + y_scale = post.image_height.to_f / post.image_height_was.to_f + + post.notes.each do |note| + note.rescale!(x_scale, y_scale) + end + end + module SearchMethods def search(params = {}) q = all @@ -88,5 +93,55 @@ class PostReplacement < ActiveRecord::Base end end + module PresenterMethods + def comment_replacement_message + %("#{creator.name}":[/users/#{creator.id}] replaced this post with a new image:\n\n#{replacement_message}) + end + + def modaction_replacement_message + "replaced post ##{post.id}:\n\n#{replacement_message}" + end + + def replacement_message + linked_source = linked_source(post.source) + linked_source_was = linked_source(post.source_was) + + <<-EOS.strip_heredoc + [table] + [tbody] + [tr] + [th]Old[/th] + [td]#{linked_source_was}[/td] + [td]#{post.md5_was}[/td] + [td]#{post.file_ext_was}[/td] + [td]#{post.image_width_was} x #{post.image_height_was}[/td] + [td]#{post.file_size_was.to_s(:human_size, precision: 4)}[/td] + [/tr] + [tr] + [th]New[/th] + [td]#{linked_source}[/td] + [td]#{post.md5}[/td] + [td]#{post.file_ext}[/td] + [td]#{post.image_width} x #{post.image_height}[/td] + [td]#{post.file_size.to_s(:human_size, precision: 4)}[/td] + [/tr] + [/tbody] + [/table] + EOS + end + + def linked_source(source) + # truncate long sources in the middle: "www.pixiv.net...lust_id=23264933" + truncated_source = source.gsub(%r{\Ahttps?://}, "").truncate(64, omission: "...#{source.last(32)}") + + if source =~ %r{\Ahttps?://}i + %("#{truncated_source}":[#{source}]) + else + truncated_source + end + end + end + + include PresenterMethods extend SearchMethods end diff --git a/app/presenters/post_presenter.rb b/app/presenters/post_presenter.rb index 69d74bb33..eaee5e70e 100644 --- a/app/presenters/post_presenter.rb +++ b/app/presenters/post_presenter.rb @@ -279,53 +279,4 @@ class PostPresenter < Presenter pool_html << "" pool_html end - - def comment_replacement_message(replacer = CurrentUser.user) - "@#{replacer.name} replaced this post with a new image:\n\n#{replacement_message}" - end - - def modaction_replacement_message - "replaced post ##{@post.id}:\n\n#{replacement_message}" - end - - def replacement_message - linked_source = linked_source(@post.source) - linked_source_was = linked_source(@post.source_was) - - <<-EOS.strip_heredoc - [table] - [tbody] - [tr] - [th]Old[/th] - [td]#{linked_source_was}[/td] - [td]#{@post.md5_was}[/td] - [td]#{@post.file_ext_was}[/td] - [td]#{@post.image_width_was} x #{@post.image_height_was}[/td] - [td]#{@post.file_size_was.to_s(:human_size, precision: 4)}[/td] - [/tr] - [tr] - [th]New[/th] - [td]#{linked_source}[/td] - [td]#{@post.md5}[/td] - [td]#{@post.file_ext}[/td] - [td]#{@post.image_width} x #{@post.image_height}[/td] - [td]#{@post.file_size.to_s(:human_size, precision: 4)}[/td] - [/tr] - [/tbody] - [/table] - EOS - end - -protected - - def linked_source(source) - # truncate long sources in the middle: "www.pixiv.net...lust_id=23264933" - truncated_source = source.gsub(%r{\Ahttps?://}, "").truncate(64, omission: "...#{source.last(32)}") - - if source =~ %r{\Ahttps?://}i - %("#{truncated_source}":[#{source}]) - else - truncated_source - end - end end diff --git a/app/views/post_replacements/_new.html.erb b/app/views/post_replacements/_new.html.erb index ce403a95b..d226ca26d 100644 --- a/app/views/post_replacements/_new.html.erb +++ b/app/views/post_replacements/_new.html.erb @@ -1,11 +1,5 @@ +<%= format_text(WikiPage.titled(Danbooru.config.replacement_notice_wiki_page).first.try(&:body), ragel: true) %> + <%= simple_form_for(post_replacement, url: post_replacements_path(post_id: post_replacement.post_id), method: :post) do |f| %> -
- Delete the current image and replace it with another one, keeping - everything else in the post intact. This is meant for upgrading - lower-quality images, such as image samples, to higher-quality versions. -
- <%= f.input :replacement_url, label: "New Source", input_html: { value: "" } %> <% end %> diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index 5e5d4be8e..63bbf297a 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -271,6 +271,10 @@ module Danbooru "help:appeal_notice" end + def replacement_notice_wiki_page + "help:replacement_notice" + end + # The number of posts displayed per page. def posts_per_page 20 diff --git a/test/unit/post_replacement_test.rb b/test/unit/post_replacement_test.rb new file mode 100644 index 000000000..3bba669f6 --- /dev/null +++ b/test/unit/post_replacement_test.rb @@ -0,0 +1,157 @@ +require 'test_helper' +require 'helpers/iqdb_test_helper' + +class PostReplacementTest < ActiveSupport::TestCase + include IqdbTestHelper + + def setup + mock_iqdb_service! + Delayed::Worker.delay_jobs = true # don't delete the old images right away + + @system = FactoryGirl.create(:user, created_at: 2.weeks.ago) + Danbooru.config.stubs(:system_user).returns(@system) + + @uploader = FactoryGirl.create(:user, created_at: 2.weeks.ago, can_upload_free: true) + @replacer = FactoryGirl.create(:user, created_at: 2.weeks.ago, can_approve_posts: true) + CurrentUser.user = @replacer + CurrentUser.ip_addr = "127.0.0.1" + end + + def teardown + CurrentUser.user = nil + CurrentUser.ip_addr = nil + Delayed::Worker.delay_jobs = false + end + + context "Replacing" do + setup do + CurrentUser.scoped(@uploader, "127.0.0.2") do + upload = FactoryGirl.create(:jpg_upload, as_pending: "0") + upload.process! + @post = upload.post + end + end + + context "a post from a generic source" do + setup do + @post.update(source: "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png") + @post.replace!(replacement_url: "https://www.google.com/intl/en_ALL/images/logo.gif") + @upload = Upload.last + @mod_action = ModAction.last + end + + context "that is then undone" do + setup do + Timecop.travel(Time.now + PostReplacement::DELETION_GRACE_PERIOD + 1.day) do + Delayed::Worker.new.work_off + end + + @replacement = @post.replacements.first + @replacement.undo! + @post.reload + end + + should "update the attributes" do + assert_equal(272, @post.image_width) + assert_equal(92, @post.image_height) + assert_equal(5969, @post.file_size) + assert_equal("png", @post.file_ext) + assert_equal("8f9327db2597fa57d2f42b4a6c5a9855", @post.md5) + assert_equal("8f9327db2597fa57d2f42b4a6c5a9855", Digest::MD5.file(@post.file_path).hexdigest) + assert_equal("https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png", @post.source) + end + end + + should "create a post replacement record" do + assert_equal(@post.id, PostReplacement.last.post_id) + end + + should "correctly update the attributes" do + assert_equal(@post.id, @upload.post.id) + assert_equal("completed", @upload.status) + + assert_equal(276, @post.image_width) + assert_equal(110, @post.image_height) + assert_equal(8558, @post.file_size) + assert_equal("gif", @post.file_ext) + assert_equal("e80d1c59a673f560785784fb1ac10959", @post.md5) + assert_equal("e80d1c59a673f560785784fb1ac10959", Digest::MD5.file(@post.file_path).hexdigest) + assert_equal("https://www.google.com/intl/en_ALL/images/logo.gif", @post.source) + end + + should "not change the post status or uploader" do + assert_equal("127.0.0.2", @post.uploader_ip_addr.to_s) + assert_equal(@uploader.id, @post.uploader_id) + assert_equal(false, @post.is_pending) + end + + should "log a mod action" do + assert_match(/replaced post ##{@post.id}/, @mod_action.description) + end + + should "leave a system comment" do + comment = @post.comments.last + + assert_not_nil(comment) + assert_equal(User.system.id, comment.creator_id) + assert_match(/replaced this post/, comment.body) + end + + should "not send an @mention to the replacer" do + assert_equal(0, @replacer.dmails.size) + end + end + + context "a post with notes" do + setup do + @post.update({image_width: 160, image_height: 164}, without_protection: true) + CurrentUser.scoped(@uploader, "127.0.0.1") do + @note = @post.notes.create(x: 80, y: 82, width: 80, height: 82, body: "test") + end + end + + should "rescale the notes" do + assert_equal([80, 82, 80, 82], [@note.x, @note.y, @note.width, @note.height]) + + assert_difference("@replacer.note_versions.count") do + # replacement image is 80x82, so we're downscaling by 50% (160x164 -> 80x82). + @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") + @note.reload + end + + assert_equal([40, 41, 40, 41], [@note.x, @note.y, @note.width, @note.height]) + end + end + + context "a post with a pixiv html source" do + should "replace with the full size image" do + @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") + + assert_equal(80, @post.image_width) + assert_equal(82, @post.image_height) + assert_equal(16275, @post.file_size) + assert_equal("png", @post.file_ext) + assert_equal("4ceadc314938bc27f3574053a3e1459a", @post.md5) + assert_equal("4ceadc314938bc27f3574053a3e1459a", Digest::MD5.file(@post.file_path).hexdigest) + assert_equal("https://i.pximg.net/img-original/img/2017/04/04/08/54/15/62247350_p0.png", @post.source) + end + + should "delete the old files after three days" do + old_file_path, old_preview_file_path, old_large_file_path = @post.file_path, @post.preview_file_path, @post.large_file_path + @post.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") + + assert(File.exists?(old_file_path)) + assert(File.exists?(old_preview_file_path)) + assert(File.exists?(old_large_file_path)) + + Timecop.travel(Time.now + PostReplacement::DELETION_GRACE_PERIOD + 1.day) do + Delayed::Worker.new.work_off + end + + assert_not(File.exists?(old_file_path)) + assert_not(File.exists?(old_preview_file_path)) + assert_not(File.exists?(old_large_file_path)) + end + end + end +end diff --git a/test/unit/post_test.rb b/test/unit/post_test.rb index 1cc8d4f0a..b0d3046bc 100644 --- a/test/unit/post_test.rb +++ b/test/unit/post_test.rb @@ -1588,129 +1588,6 @@ class PostTest < ActiveSupport::TestCase end end - context "Replacing: " do - setup do - mock_iqdb_service! - Delayed::Worker.delay_jobs = true # don't delete the old images right away - Danbooru.config.stubs(:use_s3_proxy?).returns(false) # don't fail on post ids < 10000 - - @system = FactoryGirl.create(:user, created_at: 2.weeks.ago) - Danbooru.config.stubs(:system_user).returns(@system) - - @uploader = FactoryGirl.create(:user, created_at: 2.weeks.ago, can_upload_free: true) - @replacer = FactoryGirl.create(:user, created_at: 2.weeks.ago, can_approve_posts: true) - CurrentUser.user = @replacer - CurrentUser.ip_addr = "127.0.0.1" - - CurrentUser.scoped(@uploader, "127.0.0.2") do - upload = FactoryGirl.create(:jpg_upload, as_pending: "0") - upload.process! - @post = upload.post - end - end - - teardown do - Delayed::Worker.delay_jobs = false - end - - context "replacing a post from a generic source" do - setup do - @post.update(source: "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png") - @post.replace!("https://www.google.com/intl/en_ALL/images/logo.gif") - @upload = Upload.last - @mod_action = ModAction.last - end - - context "that is then undone" do - setup do - Timecop.travel(Time.now + PostReplacement::DELETION_GRACE_PERIOD + 1.day) do - Delayed::Worker.new.work_off - end - - @replacement = @post.replacements.first - @replacement.undo! - @post.reload - end - - should "update the attributes" do - assert_equal(272, @post.image_width) - assert_equal(92, @post.image_height) - assert_equal(5969, @post.file_size) - assert_equal("png", @post.file_ext) - assert_equal("8f9327db2597fa57d2f42b4a6c5a9855", @post.md5) - assert_equal("8f9327db2597fa57d2f42b4a6c5a9855", Digest::MD5.file(@post.file_path).hexdigest) - assert_equal("https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png", @post.source) - end - end - - should "create a post replacement record" do - assert_equal(@post.id, PostReplacement.last.post_id) - end - - should "correctly update the attributes" do - assert_equal(@post.id, @upload.post.id) - assert_equal("completed", @upload.status) - - assert_equal(276, @post.image_width) - assert_equal(110, @post.image_height) - assert_equal(8558, @post.file_size) - assert_equal("gif", @post.file_ext) - assert_equal("e80d1c59a673f560785784fb1ac10959", @post.md5) - assert_equal("e80d1c59a673f560785784fb1ac10959", Digest::MD5.file(@post.file_path).hexdigest) - assert_equal("https://www.google.com/intl/en_ALL/images/logo.gif", @post.source) - end - - should "not change the post status or uploader" do - assert_equal("127.0.0.2", @post.uploader_ip_addr.to_s) - assert_equal(@uploader.id, @post.uploader_id) - assert_equal(false, @post.is_pending) - end - - should "log a mod action" do - assert_match(/replaced post ##{@post.id}/, @mod_action.description) - end - - should "leave a system comment" do - comment = @post.comments.last - - assert_not_nil(comment) - assert_equal(User.system.id, comment.creator_id) - assert_match(/@#{@replacer.name} replaced this post/, comment.body) - end - end - - context "replacing a post with a pixiv html source" do - should "replace with the full size image" do - @post.replace!("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") - - assert_equal(80, @post.image_width) - assert_equal(82, @post.image_height) - assert_equal(16275, @post.file_size) - assert_equal("png", @post.file_ext) - assert_equal("4ceadc314938bc27f3574053a3e1459a", @post.md5) - assert_equal("4ceadc314938bc27f3574053a3e1459a", Digest::MD5.file(@post.file_path).hexdigest) - assert_equal("https://i.pximg.net/img-original/img/2017/04/04/08/54/15/62247350_p0.png", @post.source) - end - - should "delete the old files after three days" do - old_file_path, old_preview_file_path, old_large_file_path = @post.file_path, @post.preview_file_path, @post.large_file_path - @post.replace!("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") - - assert(File.exists?(old_file_path)) - assert(File.exists?(old_preview_file_path)) - assert(File.exists?(old_large_file_path)) - - Timecop.travel(Time.now + PostReplacement::DELETION_GRACE_PERIOD + 1.day) do - Delayed::Worker.new.work_off - end - - assert_not(File.exists?(old_file_path)) - assert_not(File.exists?(old_preview_file_path)) - assert_not(File.exists?(old_large_file_path)) - end - end - end - context "Searching:" do setup do mock_pool_archive_service!