# frozen_string_literal: true # Redmine plugin for Document Management System "Features" # # Karel Pičman # # This file is part of Redmine DMSF plugin. # # Redmine DMSF plugin is free software: you can redistribute it and/or modify it under the terms of the GNU General # Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any # later version. # # Redmine DMSF plugin is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even # the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along with Redmine DMSF plugin. If not, see # . # Migrate documents to/from Active Storage class ActiveStorageMigration < ActiveRecord::Migration[7.0] # File system -> Active Storage def up $stdout.puts 'It could be a very long process. Be patient...' # We need to keep updated_at column unchanged and due to the asynchronous file analysis there is probably no better # way how to achieve that. add_column :dmsf_file_revisions, :temp_updated_at, :datetime, default: nil, null: true, if_not_exists: true DmsfFileRevision.update_all 'temp_updated_at = updated_at' # Remove the Xapian database as it will be rebuilt from scratch during the migration if xapian_database_removed? $stdout.puts 'The Xapian database has been removed as it will be rebuilt from scratch during the migration' end Dir.glob(DmsfFile.storage_path.join('**/*')).each do |path| # Print out the currently processed directory unless File.file?(path) $stdout.puts path next end # Process a file disk_filename = File.basename(path) DmsfFileRevision.where(disk_filename: disk_filename) .order(source_dmsf_file_revision_id: :asc) .find_each .with_index do |r, i| if i.zero? r.shared_file.attach( io: File.open(path), filename: r.name, content_type: r.content_type || 'application/octet-stream', identify: false ) # Remove the original file FileUtils.rm path key = r.file.blob.key $stdout.puts "#{path} => #{File.join(key[0..1], key[2..3], key)} (#{r.file.blob.filename})" else # The other revisions should have set the source revision warn("r#{r.id}.source_dmsf_file_revision_id is null") unless r.source_dmsf_file_revision_id end end end # Remove columns duplicated in ActiveStorage remove_column :dmsf_file_revisions, :digest remove_column :dmsf_file_revisions, :mime_type remove_column :dmsf_file_revisions, :disk_filename remove_column :dmsf_files, :name # We need to keep the size despite the fact that it's duplicated in active_storage_blobs to speed up the main # document view # Restore updated_at column DmsfFileRevision.update_all 'updated_at = temp_updated_at' remove_column :dmsf_file_revisions, :temp_updated_at $stdout.puts 'Done' end # Active Storage -> File system def down $stdout.puts 'It could be a very long process. Be patient...' # Restore removed columns add_column :dmsf_file_revisions, :digest, :string, limit: 64, default: '', null: false add_column :dmsf_file_revisions, :mime_type, :string add_column :dmsf_file_revisions, :disk_filename, :string, default: '', null: false add_column :dmsf_files, :name, :string, default: '', null: false # Migrate attachments ActiveStorage::Attachment.find_each do |a| r = a.record new_path = disk_file unless File.exist?(new_path) a.blob.open do |f| # Move the attachment FileUtils.mv f.path, new_path r.record_timestamps = false # Do not modify updated_at column DmsfFileRevision.no_touching do # Mime type r.mime_type = f.content_type # Disk filename r.disk_filename = File.basename(new_path) # Digest # We leave the digest calculation to dmsf_create_digests.rake task r.save end r.dmsf_file.record_timestamps = false # Do not modify updated_at column DmsfFile.no_touching do # Filename r.dmsf_file.name = r.dmsf_file.last_revision.name r.dmsf_file.save end end key = a.blob.key $stdout.puts "#{File.join(key[0..1], key[2..3], key)} (#{a.blob.filename}) => #{new_path}" end # Remove the original file r.record_timestamps = false # Do not modify updated_at column DmsfFileRevision.no_touching do a.purge end end # Remove the Xapian database as it is useless now and has to be rebuilt with xapian_indexer.rb if xapian_database_removed? $stdout.puts 'Xapian database have been removed as it is useless now and has to be rebuilt with xapian_indexer.rb' end $stdout.puts 'Done' end private # Delete Xapian database def xapian_database_removed? if RedmineDmsf.xapian_available FileUtils.rm_rf File.join(RedmineDmsf.dmsf_index_database, RedmineDmsf.dmsf_stemming_lang) true else false end end def storage_base_path(dmsf_file_revision) time = dmsf_file_revision.created_at || DateTime.current DmsfFile.storage_path.join(time.strftime('%Y')).join time.strftime('%m') end def disk_file(dmsf_file_revision) path = storage_base_path begin FileUtils.mkdir_p path rescue StandardError => e Rails.logger.error e.message end path.join(dmsf_file_revision.disk_filename).to_s end end