diff --git a/Gemfile b/Gemfile index 98f7c7c67..9a86e1b40 100644 --- a/Gemfile +++ b/Gemfile @@ -14,7 +14,6 @@ gem 'ruby-vips' gem 'net-sftp' gem 'diff-lcs', :require => "diff/lcs/array" gem 'bcrypt', :require => "bcrypt" -gem 'statistics2' gem 'capistrano', '~> 3.10' gem 'capistrano-rails' gem 'capistrano-rbenv' diff --git a/Gemfile.lock b/Gemfile.lock index e5036ca3f..4b18fb70e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -361,7 +361,6 @@ GEM net-scp (>= 1.1.2) net-ssh (>= 2.8.0) stackprof (0.2.15) - statistics2 (0.54) streamio-ffmpeg (3.0.2) multi_json (~> 1.8) stripe (5.14.0) @@ -466,7 +465,6 @@ DEPENDENCIES simplecov sinatra stackprof - statistics2 streamio-ffmpeg stripe unicorn diff --git a/app/logical/danbooru_math.rb b/app/logical/danbooru_math.rb deleted file mode 100644 index d7f1238c8..000000000 --- a/app/logical/danbooru_math.rb +++ /dev/null @@ -1,11 +0,0 @@ -class DanbooruMath - def self.ci_lower_bound(pos, n, confidence = 0.95) - if n == 0 - return 0 - end - - z = Statistics2.pnormaldist(1 - (1 - confidence) / 2) - phat = 1.0 * pos / n - 100 * (phat + z * z / (2 * n) - z * Math.sqrt((phat * (1 - phat) + z * z / (4 * n)) / n)) / (1 + z * z / n) - end -end diff --git a/app/logical/upload_limit.rb b/app/logical/upload_limit.rb index 2bdce1f47..014706210 100644 --- a/app/logical/upload_limit.rb +++ b/app/logical/upload_limit.rb @@ -11,7 +11,23 @@ class UploadLimit end def limited? - used_upload_slots >= upload_slots + if user.can_upload_free? + false + elsif user.created_at > 1.week.ago + true + else + used_upload_slots >= upload_slots + end + end + + def limit_reason + if user.created_at > 1.week.ago + "cannot upload during your first week of registration" + elsif limited? + "have reached your upload limit" + else + nil + end end def used_upload_slots diff --git a/app/models/post.rb b/app/models/post.rb index bdcfa2632..01fc2c887 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -27,6 +27,7 @@ class Post < ApplicationRecord validate :has_enough_tags validate :post_is_not_its_own_parent validate :updater_can_change_rating + validate :uploader_is_not_limited, on: :create before_save :update_tag_post_counts before_save :set_tag_counts before_create :autoban @@ -1280,7 +1281,7 @@ class Post < ApplicationRecord give_favorites_to_parent(options) if options[:move_favorites] is_automatic = (reason == "Unapproved in three days") - uploader.new_upload_limit.update_limit!(self, incremental: is_automatic) + uploader.upload_limit.update_limit!(self, incremental: is_automatic) unless options[:without_mod_action] ModAction.log("deleted post ##{id}, reason: #{reason}", :post_delete) @@ -1648,6 +1649,10 @@ class Post < ApplicationRecord end end + def uploader_is_not_limited + errors[:uploader] << uploader.upload_limit.limit_reason if uploader.upload_limit.limited? + end + def added_tags_are_valid new_tags = added_tags.select { |t| t.post_count <= 0 } new_general_tags = new_tags.select { |t| t.category == Tag.categories.general } diff --git a/app/models/post_approval.rb b/app/models/post_approval.rb index df65375f5..ddf658a7e 100644 --- a/app/models/post_approval.rb +++ b/app/models/post_approval.rb @@ -32,7 +32,7 @@ class PostApproval < ApplicationRecord post.update(approver: user, is_flagged: false, is_pending: false, is_deleted: false) ModAction.log("undeleted post ##{post_id}", :post_undelete) if is_undeletion - post.uploader.new_upload_limit.update_limit!(post, incremental: !is_undeletion) + post.uploader.upload_limit.update_limit!(post, incremental: !is_undeletion) end def self.search(params) diff --git a/app/models/upload.rb b/app/models/upload.rb index ae6628e61..9d0f0bcc4 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -60,7 +60,6 @@ class Upload < ApplicationRecord before_validation :initialize_attributes, on: :create before_validation :assign_rating_from_tags - validate :uploader_is_not_limited, on: :create # validates :source, format: { with: /\Ahttps?/ }, if: ->(record) {record.file.blank?}, on: :create validates :rating, inclusion: { in: %w(q e s) }, allow_nil: true validates :md5, confirmation: true, if: ->(rec) { rec.md5_confirmation.present? } @@ -235,12 +234,6 @@ class Upload < ApplicationRecord extend SearchMethods include SourceMethods - def uploader_is_not_limited - if !uploader.can_upload? - errors.add(:uploader, uploader.upload_limited_reason) - end - end - def assign_rating_from_tags if rating = Tag.has_metatag?(tag_string, :rating) self.rating = rating.downcase.first diff --git a/app/models/user.rb b/app/models/user.rb index c81987325..4f040511b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -417,26 +417,6 @@ class User < ApplicationRecord end end - def can_upload? - if can_upload_free? - true - elsif is_admin? - true - elsif created_at > 1.week.ago - false - else - upload_limit > 0 - end - end - - def upload_limited_reason - if created_at > 1.week.ago - "cannot upload during your first week of registration" - else - "have reached your upload limit for the day" - end - end - def can_comment? if is_gold? true @@ -469,38 +449,8 @@ class User < ApplicationRecord (is_moderator? && flag.not_uploaded_by?(id)) || flag.creator_id == id end - def new_upload_limit - @new_upload_limit ||= UploadLimit.new(self) - end - def upload_limit - [max_upload_limit - used_upload_slots, 0].max - end - - def used_upload_slots - uploaded_count = posts.where("created_at >= ?", 23.hours.ago).count - uploaded_comic_count = posts.tag_match("comic").where("created_at >= ?", 23.hours.ago).count / 3 - uploaded_count - uploaded_comic_count - end - memoize :used_upload_slots - - def max_upload_limit - [(base_upload_limit * upload_limit_multiplier).ceil, 10].max - end - - def upload_limit_multiplier - (1 - (adjusted_deletion_confidence / 15.0)) - end - - def adjusted_deletion_confidence - [deletion_confidence(60.days.ago), 15].min - end - memoize :adjusted_deletion_confidence - - def deletion_confidence(date) - deletions = posts.deleted.where("created_at >= ?", date).count - total = posts.where("created_at >= ?", date).count - DanbooruMath.ci_lower_bound(deletions, total) + @upload_limit ||= UploadLimit.new(self) end def base_upload_limit @@ -624,7 +574,6 @@ class User < ApplicationRecord forum_post_count comment_count favorite_group_count appeal_count flag_count positive_feedback_count neutral_feedback_count negative_feedback_count upload_limit - max_upload_limit ] end diff --git a/app/presenters/user_presenter.rb b/app/presenters/user_presenter.rb index 48c04953a..dabe583d8 100644 --- a/app/presenters/user_presenter.rb +++ b/app/presenters/user_presenter.rb @@ -43,22 +43,6 @@ class UserPresenter Post.tag_match("search:#{category}").limit(10) end - def upload_limit(template) - if user.can_upload_free? - return "none" - end - - slots_tooltip = "Next free slot: #{template.time_ago_in_words(user.next_free_upload_slot)}" - limit_tooltip = <<-EOS.strip_heredoc - Base: #{user.base_upload_limit} - Del. Rate: #{format("%.2f", user.adjusted_deletion_confidence)} - Multiplier: (1 - (#{format("%.2f", user.adjusted_deletion_confidence)} / 15)) = #{user.upload_limit_multiplier} - Upload Limit: #{user.base_upload_limit} * #{format("%.2f", user.upload_limit_multiplier)} = #{user.max_upload_limit} - EOS - - %{#{user.used_upload_slots} / #{user.max_upload_limit}}.html_safe - end - def uploads Post.tag_match("user:#{user.name}").limit(6) end diff --git a/app/views/uploads/new.html.erb b/app/views/uploads/new.html.erb index 10144ff66..a09e3e1dc 100644 --- a/app/views/uploads/new.html.erb +++ b/app/views/uploads/new.html.erb @@ -2,11 +2,13 @@

Upload

- <% if CurrentUser.can_upload? %> + <% if !CurrentUser.user.upload_limit.limited? %> <%= embed_wiki("help:upload_notice", id: "upload-guide-notice") %> <% unless CurrentUser.can_upload_free? %> -

Upload limit: <%= CurrentUser.user.presenter.upload_limit(self) %>.

+

+ Upload Limit: <%= render "users/upload_limit", user: CurrentUser.user %> +

<% end %> <%= render "image" %> @@ -80,7 +82,7 @@ <%= render "related_tags/container" %> <% end %> <% else %> -

You <%= CurrentUser.user.upload_limited_reason %>

+

You <%= CurrentUser.user.upload_limit.limit_reason %>

<% end %>
diff --git a/app/views/users/_statistics.html.erb b/app/views/users/_statistics.html.erb index 5831f5dfb..f408e2ea8 100644 --- a/app/views/users/_statistics.html.erb +++ b/app/views/users/_statistics.html.erb @@ -63,15 +63,8 @@ Upload Limit - <%= presenter.upload_limit(self) %> (<%= link_to_wiki "help", "about:upload_limits" %>) - - - - New Upload Limit - <%= link_to user.new_upload_limit.used_upload_slots, posts_path(tags: "user:#{user.name} status:pending") %> - / - <%= tag.abbr user.new_upload_limit.upload_slots, title: "Next level: #{user.new_upload_limit.approvals_on_current_level} / #{user.new_upload_limit.approvals_for_next_level} approvals" %> + <%= render "users/upload_limit", user: user %> diff --git a/app/views/users/_upload_limit.html.erb b/app/views/users/_upload_limit.html.erb new file mode 100644 index 000000000..60fd44afa --- /dev/null +++ b/app/views/users/_upload_limit.html.erb @@ -0,0 +1,10 @@ +<%# user %> + +<% if user.can_upload_free? %> + none +<% else %> + <%= link_to user.upload_limit.used_upload_slots, posts_path(tags: "user:#{user.name} status:pending") %> / + <%= tag.abbr user.upload_limit.upload_slots, title: "#{pluralize(user.upload_limit.approvals_for_next_level - user.upload_limit.approvals_on_current_level, "approved post")} needed for next level (progress: #{user.upload_limit.approvals_on_current_level} / #{user.upload_limit.approvals_for_next_level}) " %> +<% end %> + +(<%= link_to_wiki "help", "about:upload_limits" %>) diff --git a/config/locales/en.yml b/config/locales/en.yml index e6f86ff2e..22dfa4464 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -43,6 +43,8 @@ en: approver: "You" approver_id: "You" updater_id: "You" + uploader: "You" + uploader_id: "You" post_flag: creator: "You" creator_id: "You" diff --git a/script/fixes/062_initialize_upload_points.rb b/script/fixes/062_initialize_upload_points.rb index f439f7fc2..78431d523 100755 --- a/script/fixes/062_initialize_upload_points.rb +++ b/script/fixes/062_initialize_upload_points.rb @@ -6,7 +6,7 @@ uploaders = User.where(id: Post.select(:uploader_id)).bit_prefs_match(:can_uploa warn "uploaders=#{uploaders.count}" uploaders.find_each.with_index do |uploader, n| - uploader.new_upload_limit.update_limit!(nil, incremental: false) + uploader.upload_limit.update_limit!(nil, incremental: false) warn "n=#{n} id=#{uploader.id} name=#{uploader.name} points=#{uploader.upload_points}" end diff --git a/test/factories/post.rb b/test/factories/post.rb index 96260eb40..ab9fa3da8 100644 --- a/test/factories/post.rb +++ b/test/factories/post.rb @@ -1,5 +1,6 @@ FactoryBot.define do factory(:post) do + created_at { 2.weeks.ago } sequence :md5 do |n| n.to_s end diff --git a/test/factories/user.rb b/test/factories/user.rb index 6a966a7af..94a4fa7bd 100644 --- a/test/factories/user.rb +++ b/test/factories/user.rb @@ -1,5 +1,5 @@ FactoryBot.define do - factory(:user, aliases: [:creator, :updater, :uploader]) do + factory(:user, aliases: [:creator, :updater]) do sequence :name do |n| "user#{n}" end @@ -58,5 +58,9 @@ FactoryBot.define do level {50} can_approve_posts {true} end + + factory(:uploader) do + created_at { 2.weeks.ago } + end end end diff --git a/test/functional/uploads_controller_test.rb b/test/functional/uploads_controller_test.rb index 4883db776..868c092d6 100644 --- a/test/functional/uploads_controller_test.rb +++ b/test/functional/uploads_controller_test.rb @@ -223,6 +223,22 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest end end + context "when the uploader is limited" do + should "not allow uploading" do + @member = create(:user, created_at: 2.weeks.ago, upload_points: 0) + create_list(:post, @member.upload_limit.upload_slots, uploader: @member, is_pending: true) + + assert_no_difference("Post.count") do + file = Rack::Test::UploadedFile.new("#{Rails.root}/test/files/test.jpg", "image/jpeg") + post_auth uploads_path, @member, params: { upload: { file: file, tag_string: "aaa", rating: "q" }} + end + + @upload = Upload.last + assert_redirected_to @upload + assert_match(/have reached your upload limit/, @upload.status) + end + end + should "create a new upload" do assert_difference("Upload.count", 1) do file = Rack::Test::UploadedFile.new("#{Rails.root}/test/files/test.jpg", "image/jpeg") diff --git a/test/unit/upload_limit_test.rb b/test/unit/upload_limit_test.rb index 2df738ab7..f58c8c95a 100644 --- a/test/unit/upload_limit_test.rb +++ b/test/unit/upload_limit_test.rb @@ -3,7 +3,7 @@ require 'test_helper' class UploadLimitTest < ActiveSupport::TestCase context "Upload limits:" do setup do - @user = create(:user, upload_points: 1000) + @user = create(:user, upload_points: 1000, created_at: 2.weeks.ago) @approver = create(:moderator_user) end diff --git a/test/unit/upload_test.rb b/test/unit/upload_test.rb deleted file mode 100644 index 51b5a46f2..000000000 --- a/test/unit/upload_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'test_helper' - -class UploadTest < ActiveSupport::TestCase - SOURCE_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/NAMA_Machine_d%27Anticyth%C3%A8re_1.jpg/538px-NAMA_Machine_d%27Anticyth%C3%A8re_1.jpg?download" - - context "In all cases" do - setup do - mock_iqdb_service! - user = FactoryBot.create(:contributor_user) - CurrentUser.user = user - CurrentUser.ip_addr = "127.0.0.1" - end - - teardown do - CurrentUser.user = nil - CurrentUser.ip_addr = nil - end - - context "An upload" do - context "from a user that is limited" do - setup do - CurrentUser.user = FactoryBot.create(:user, :created_at => 1.year.ago) - User.any_instance.stubs(:upload_limit).returns(0) - end - - should "fail creation" do - @upload = FactoryBot.build(:jpg_upload, :tag_string => "") - @upload.save - assert_equal(["You have reached your upload limit for the day"], @upload.errors.full_messages) - end - end - end - end -end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index 4d58a81f4..8c8056f54 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -48,24 +48,6 @@ class UserTest < ActiveSupport::TestCase end end - should "limit post uploads" do - assert(!@user.can_upload?) - @user.update_column(:created_at, 15.days.ago) - assert(@user.can_upload?) - assert_equal(10, @user.upload_limit) - - 9.times do - FactoryBot.create(:post, :uploader => @user, :is_pending => true) - end - - @user = User.find(@user.id) - assert_equal(1, @user.upload_limit) - assert(@user.can_upload?) - FactoryBot.create(:post, :uploader => @user, :is_pending => true) - @user = User.find(@user.id) - assert(!@user.can_upload?) - end - should "limit comment votes" do Danbooru.config.stubs(:member_comment_time_threshold).returns(1.week.from_now) Danbooru.config.stubs(:member_comment_limit).returns(10)