This commit is contained in:
Daniel King 2025-11-26 17:05:14 +00:00 committed by GitHub
commit 11ac4302be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 56 additions and 1 deletions

View File

@ -90,6 +90,7 @@ class Status < ApplicationRecord
has_many :local_favorited, -> { merge(Account.local) }, through: :favourites, source: :account
has_many :local_reblogged, -> { merge(Account.local) }, through: :reblogs, source: :account
has_many :local_bookmarked, -> { merge(Account.local) }, through: :bookmarks, source: :account
has_many :local_replied, -> { merge(Account.local) }, through: :replies, source: :account
has_and_belongs_to_many :tags # rubocop:disable Rails/HasAndBelongsToMany
@ -136,6 +137,12 @@ class Status < ApplicationRecord
scope :tagged_with_none, lambda { |tag_ids|
where('NOT EXISTS (SELECT * FROM statuses_tags forbidden WHERE forbidden.status_id = statuses.id AND forbidden.tag_id IN (?))', tag_ids)
}
scope :with_local_interaction, lambda {
Status.where(id: Status.joins(:local_favorited).select(:id))
.or(Status.where(id: Status.joins(:local_bookmarked).select(:id)))
.or(Status.where(id: Status.joins(:local_replied).select(:id)))
.or(Status.where(id: Status.joins(:local_reblogged).select(:id)))
}
after_create_commit :trigger_create_webhooks
after_update_commit :trigger_update_webhooks

View File

@ -17,6 +17,7 @@ module Mastodon::CLI
option :concurrency, type: :numeric, default: 5, aliases: [:c]
option :verbose, type: :boolean, default: false, aliases: [:v]
option :dry_run, type: :boolean, default: false
option :keep_interacted, type: :boolean, default: false
desc 'remove', 'Remove remote media files, headers or avatars'
long_desc <<-DESC
Removes locally cached copies of media attachments (and optionally profile
@ -26,6 +27,9 @@ module Mastodon::CLI
they are removed. In case of avatars and headers, it specifies how old
the last webfinger request and update to the user has to be before they
are pruned. It defaults to 7 days.
If --keep-interacted is specified, any media attached to a status that
was favourited, bookmarked, replied to, or reblogged by a local account
will be preserved.
If --prune-profiles is specified, only avatars and headers are removed.
If --remove-headers is specified, only headers are removed.
If --include-follows is specified along with --prune-profiles or
@ -61,7 +65,11 @@ module Mastodon::CLI
end
unless options[:prune_profiles] || options[:remove_headers]
processed, aggregate = parallelize_with_progress(MediaAttachment.cached.remote.where(created_at: ..time_ago)) do |media_attachment|
attachment_scope = MediaAttachment.cached.remote.where(created_at: ..time_ago)
attachment_scope = attachment_scope.where.not(status_id: Status.with_local_interaction.select(:id)) if options[:keep_interacted]
processed, aggregate = parallelize_with_progress(attachment_scope) do |media_attachment|
next if media_attachment.file.blank?
size = (media_attachment.file_file_size || 0) + (media_attachment.thumbnail_file_size || 0)

View File

@ -73,6 +73,46 @@ RSpec.describe Mastodon::CLI::Media do
expect(media_attachment.reload.thumbnail).to be_blank
end
end
context 'with --keep-interacted' do
let(:options) { { keep_interacted: true } }
let!(:local_account) { Fabricate(:account, username: 'alice') }
let!(:remote_account) { Fabricate(:account, username: 'bob', domain: 'example.com') }
let!(:favourited_status) { Fabricate(:status, account: remote_account) }
let!(:favourited_media) { Fabricate(:media_attachment, created_at: 1.month.ago, remote_url: 'https://example.com/image.jpg', status: favourited_status) }
let!(:bookmarked_status) { Fabricate(:status, account: remote_account) }
let!(:bookmarked_media) { Fabricate(:media_attachment, created_at: 1.month.ago, remote_url: 'https://example.com/image.jpg', status: bookmarked_status) }
let!(:replied_to_status) { Fabricate(:status, account: remote_account) }
let!(:replied_to_media) { Fabricate(:media_attachment, created_at: 1.month.ago, remote_url: 'https://example.com/image.jpg', status: replied_to_status) }
let!(:reblogged_status) { Fabricate(:status, account: remote_account) }
let!(:reblogged_media) { Fabricate(:media_attachment, created_at: 1.month.ago, remote_url: 'https://example.com/image.jpg', status: reblogged_status) }
let!(:non_interacted_status) { Fabricate(:status, account: remote_account) }
let!(:non_interacted_media) { Fabricate(:media_attachment, created_at: 1.month.ago, remote_url: 'https://example.com/image.jpg', status: non_interacted_status) }
before do
Fabricate(:favourite, account: local_account, status: favourited_status)
Fabricate(:bookmark, account: local_account, status: bookmarked_status)
Fabricate(:status, account: local_account, in_reply_to_id: replied_to_status.id)
Fabricate(:status, account: local_account, reblog: reblogged_status)
end
it 'keeps media associated with statuses that have been favourited, bookmarked, replied to, or reblogged by a local account' do
expect { subject }
.to output_results('Removed 1')
expect(favourited_media.reload.file).to be_present
expect(bookmarked_media.reload.file).to be_present
expect(replied_to_media.reload.file).to be_present
expect(reblogged_media.reload.file).to be_present
expect(non_interacted_media.reload.file).to be_blank
end
end
end
end