OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openproject/db/migrate/migration_utils/attachable_utils.rb

203 lines
7.1 KiB

#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require_relative 'utils'
module Migration::Utils
module AttachableUtils
MissingAttachment = Struct.new(:journaled_id,
:journaled_type,
:attachment_id,
:filename,
:last_version)
def add_missing_attachable_journals
result = missing_attachments
repair_journals(result)
end
def repair_attachable_journal_entries(journal_type, legacy_journal_type)
result = invalid_attachments(legacy_journal_type)
result.map do |m| m.journaled_type = journal_type end
repair_journals(result)
end
def remove_initial_journal_entries(journal_type, legacy_journal_type)
result = invalid_attachments(legacy_journal_type)
result.map do |m| m.journaled_type = journal_type end
remove_initial_journals(result)
end
def repair_journals(result)
result.each do |m|
journal_ids = affected_journal_ids(m.journaled_id, m.last_version, m.journaled_type)
journal_ids.each do |journal_id|
insert <<-SQL
INSERT INTO attachable_journals (journal_id, attachment_id, filename)
VALUES (#{journal_id}, #{m.attachment_id}, '#{m.filename}')
SQL
end
end
end
def remove_initial_journals(result)
result.each do |m|
journal_ids = affected_journal_ids(m.journaled_id, m.last_version, m.journaled_type)
delete <<-SQL
DELETE FROM attachable_journals
WHERE journal_id IN (#{journal_ids.join(', ')})
SQL
end
end
private
def missing_attachments
result = select_all <<-SQL
SELECT * FROM (
SELECT a.container_id AS journaled_id, a.container_type AS journaled_type, a.id AS attachment_id, a.filename, MAX(aj.id) AS aj_id, MAX(j.version) AS last_version
FROM attachments AS a JOIN journals AS j
ON (a.container_id = j.journable_id AND a.container_type = j.journable_type) LEFT JOIN attachable_journals AS aj
ON (a.id = aj.attachment_id)
GROUP BY a.container_id, a.container_type, a.id, a.filename
) AS tmp
WHERE aj_id IS NULL
SQL
result.map { |row|
MissingAttachment.new(row['journaled_id'],
row['journaled_type'],
row['attachment_id'],
row['filename'],
row['last_version'])
}
end
COLUMNS = ['changed_data', 'version', 'journaled_id']
def invalid_attachments(legacy_journal_type)
result = []
update_column_values('legacy_journals',
COLUMNS,
find_work_packages_with_missing_initial_attachment(legacy_journal_type,
result),
journal_filter(legacy_journal_type))
result.flatten
end
def journal_filter(legacy_journal_type)
"type = '#{legacy_journal_type}' AND changed_data LIKE '%attachments%'"
end
def find_work_packages_with_missing_initial_attachment(legacy_journal_type, result)
Proc.new do |row|
missing_entries = missing_initial_attachable_journals(legacy_journal_type,
row['id'],
row['journaled_id'],
row['version'],
row['changed_data'])
result << missing_entries unless missing_entries.empty?
UpdateResult.new(row, false)
end
end
def missing_initial_attachable_journals(legacy_journal_type, _journal_id, journaled_id, version, changed_data)
removed_attachments = parse_attachment_removals(changed_data)
missing_entries = missing_initial_attachment_entries(legacy_journal_type,
journaled_id,
version,
removed_attachments)
missing_entries.map { |e|
MissingAttachment.new(journaled_id,
nil,
e[:id],
e[:filename],
version.to_i - 1)
}
end
############################################
# Matches attachment removals of the form: #
# #
# attachments<id>: #
# - #
# - <filename> #
############################################
ATTACHMENT_REMOVAL_REGEX = /attachments_(?<id>\d+): \n-\s(?<filename>.+)\n-\s$/
def parse_attachment_removals(changed_data)
matches = changed_data.scan(ATTACHMENT_REMOVAL_REGEX)
matches.each_with_object([]) { |m, l| l << { id: m[0], filename: m[1] } }
end
def missing_initial_attachment_entries(legacy_journal_type, journaled_id, version, attachments)
attachments.select do |a|
result = select_all <<-SQL
SELECT version
FROM legacy_journals
WHERE journaled_id = #{journaled_id}
AND type = '#{legacy_journal_type}'
AND version < #{version}
AND changed_data LIKE '%attachments#{a[:id]}:%'
AND changed_data LIKE '%- #{a[:filename]}%'
ORDER BY version
SQL
result.empty?
end
end
def affected_journal_ids(journaled_id, last_version, journal_type)
result_set = select_all <<-SQL
SELECT id
FROM journals
WHERE journable_id = #{journaled_id}
AND journable_type = '#{journal_type}'
AND version <= #{last_version}
SQL
result_set.map { |r| r['id'] }
end
end
end