2019-05-02 17:17:27 -05:00
# frozen_string_literal: true
2018-09-12 03:51:53 -05:00
class UploadRecovery
2019-08-01 13:24:06 -05:00
def initialize ( dry_run : false , stop_on_error : false )
2018-09-12 08:53:01 -05:00
@dry_run = dry_run
2019-08-01 13:24:06 -05:00
@stop_on_error = stop_on_error
2018-09-12 08:53:01 -05:00
end
2018-09-13 03:32:35 -05:00
def recover ( posts = Post )
2019-10-01 23:57:36 -05:00
posts . have_uploads . find_each { | post | recover_post post }
end
2019-04-01 22:29:26 -05:00
2019-10-01 23:57:36 -05:00
def recover_post ( post )
begin
analyzer = PostAnalyzer . new ( post . raw , post . topic_id )
2018-09-12 03:51:53 -05:00
2023-01-09 06:10:19 -06:00
analyzer
. cooked_stripped
. css ( " img " , " a " )
. each do | media |
if media . name == " img " && orig_src = media [ " data-orig-src " ]
if dom_class = media [ " class " ]
next if ( Post . allowed_image_classes & dom_class . split ) . count > 0
2018-09-12 22:57:51 -05:00
end
2018-09-18 21:44:36 -05:00
2019-10-01 23:57:36 -05:00
if @dry_run
2023-01-09 06:10:19 -06:00
puts " #{ post . full_url } #{ orig_src } "
2019-10-01 23:57:36 -05:00
else
2023-01-09 06:10:19 -06:00
recover_post_upload ( post , Upload . sha1_from_short_url ( orig_src ) )
end
elsif url = ( media [ " href " ] || media [ " src " ] )
data = Upload . extract_url ( url )
next unless data
upload = Upload . get_from_url ( url )
if ! upload || upload . verification_status == Upload . verification_statuses [ :invalid_etag ]
if @dry_run
puts " #{ post . full_url } #{ url } "
else
sha1 = data [ 2 ]
recover_post_upload ( post , sha1 )
end
2018-09-12 22:57:51 -05:00
end
2018-09-12 08:53:01 -05:00
end
2018-09-12 03:51:53 -05:00
end
2019-10-01 23:57:36 -05:00
rescue = > e
raise e if @stop_on_error
puts " #{ post . full_url } #{ e . class } : #{ e . message } "
2018-09-12 03:51:53 -05:00
end
end
private
2018-09-18 21:44:36 -05:00
def recover_post_upload ( post , sha1 )
2018-09-30 21:51:25 -05:00
return unless valid_sha1? ( sha1 )
2018-09-13 00:59:17 -05:00
2023-01-09 06:10:19 -06:00
attributes = { post : post , sha1 : sha1 }
2018-09-12 03:51:53 -05:00
if Discourse . store . external?
2021-09-27 07:45:05 -05:00
recover_post_upload_from_s3 ( ** attributes )
2018-09-12 03:51:53 -05:00
else
2021-09-27 07:45:05 -05:00
recover_post_upload_from_local ( ** attributes )
2018-09-30 21:51:25 -05:00
end
end
2019-05-22 00:24:36 -05:00
def ensure_upload! ( post : , sha1 : , upload : )
return if ! upload . persisted?
if upload . sha1 != sha1
2019-05-22 00:51:09 -05:00
STDERR . puts " Warning #{ post . url } had an incorrect #{ sha1 } should be #{ upload . sha1 } storing in custom field 'rake uploads:fix_relative_upload_links' can fix this "
sha_map = post . custom_fields [ " UPLOAD_SHA1_MAP " ] || " {} "
sha_map = JSON . parse ( sha_map )
sha_map [ sha1 ] = upload . sha1
post . custom_fields [ " UPLOAD_SHA1_MAP " ] = sha_map . to_json
post . save_custom_fields
2019-05-22 00:24:36 -05:00
end
post . rebake!
end
2018-09-30 21:51:25 -05:00
def recover_post_upload_from_local ( post : , sha1 : )
recover_from_local ( sha1 : sha1 , user_id : post . user_id ) do | upload |
2019-05-22 00:24:36 -05:00
ensure_upload! ( post : post , sha1 : sha1 , upload : upload )
2018-09-30 21:51:25 -05:00
end
end
def recover_post_upload_from_s3 ( post : , sha1 : )
recover_from_s3 ( sha1 : sha1 , user_id : post . user_id ) do | upload |
2019-05-22 00:24:36 -05:00
ensure_upload! ( post : post , sha1 : sha1 , upload : upload )
2018-09-12 03:51:53 -05:00
end
end
2018-09-30 21:51:25 -05:00
def recover_from_local ( sha1 : , user_id : )
2023-01-09 06:10:19 -06:00
@paths || =
begin
Dir . glob ( File . join ( Discourse . store . tombstone_dir , " original " , " ** " , " *.* " ) ) . concat (
Dir . glob ( File . join ( Discourse . store . upload_path , " original " , " ** " , " *.* " ) ) ,
)
end
2018-09-12 03:51:53 -05:00
@paths . each do | path |
if path =~ / #{ sha1 } /
begin
2018-09-19 02:46:23 -05:00
tmp = Tempfile . new
tmp . write ( File . read ( path ) )
tmp . rewind
2018-09-30 21:51:25 -05:00
upload = create_upload ( tmp , File . basename ( path ) , user_id )
yield upload if block_given?
2018-09-12 03:51:53 -05:00
ensure
2018-09-19 02:46:23 -05:00
tmp & . close
2018-09-12 03:51:53 -05:00
end
end
end
end
2018-09-30 21:51:25 -05:00
def recover_from_s3 ( sha1 : , user_id : )
2023-01-09 06:10:19 -06:00
@object_keys || =
begin
s3_helper = Discourse . store . s3_helper
if Rails . configuration . multisite
current_db = RailsMultisite :: ConnectionManagement . current_db
s3_helper
. list ( " uploads/ #{ current_db } /original " )
. map ( & :key )
. concat (
s3_helper . list (
" uploads/ #{ FileStore :: S3Store :: TOMBSTONE_PREFIX } #{ current_db } /original " ,
) . map ( & :key ) ,
)
else
s3_helper
. list ( " original " )
. map ( & :key )
. concat ( s3_helper . list ( " #{ FileStore :: S3Store :: TOMBSTONE_PREFIX } original " ) . map ( & :key ) )
end
2019-08-01 20:38:21 -05:00
end
2018-09-12 03:51:53 -05:00
2020-10-01 07:54:45 -05:00
upload_exists = Upload . exists? ( sha1 : sha1 )
2018-09-12 03:51:53 -05:00
@object_keys . each do | key |
if key =~ / #{ sha1 } /
tombstone_prefix = FileStore :: S3Store :: TOMBSTONE_PREFIX
2018-10-01 06:03:02 -05:00
if key . include? ( tombstone_prefix )
2018-09-12 20:19:45 -05:00
old_key = key
key = key . sub ( tombstone_prefix , " " )
2023-06-06 00:47:40 -05:00
Discourse . store . s3_helper . copy (
old_key ,
key ,
options : {
acl : SiteSetting . s3_use_acls ? " public-read " : nil ,
} ,
)
2018-09-12 03:51:53 -05:00
end
2020-10-01 07:54:45 -05:00
next if upload_exists
2018-09-12 03:51:53 -05:00
url = " https: #{ SiteSetting . Upload . absolute_base_url } / #{ key } "
begin
2023-01-09 06:10:19 -06:00
tmp =
FileHelper . download (
url ,
max_file_size : SiteSetting . max_image_size_kb . kilobytes ,
tmp_file_name : " recover_from_s3 " ,
)
2018-09-12 03:51:53 -05:00
2018-09-30 21:51:25 -05:00
if tmp
upload = create_upload ( tmp , File . basename ( key ) , user_id )
yield upload if block_given?
end
2018-09-12 03:51:53 -05:00
ensure
tmp & . close
end
end
end
end
2018-09-30 21:51:25 -05:00
def create_upload ( file , filename , user_id )
UploadCreator . new ( file , filename ) . create_for ( user_id )
end
def valid_sha1? ( sha1 )
sha1 . present? && sha1 . length == Upload :: SHA1_LENGTH
2018-09-12 03:51:53 -05:00
end
end