#9 Active Storage - Duplicated columns removed
This commit is contained in:
parent
54ead3ded4
commit
3a50485653
@ -67,7 +67,7 @@ class DmsfFilesController < ApplicationController
|
||||
Rails.logger.error "Could not send email notifications: #{e.message}"
|
||||
end
|
||||
# Allow a preview of the file by an external plugin
|
||||
results = call_hook(:dmsf_files_controller_before_view, { file: @revision.disk_file })
|
||||
results = call_hook(:dmsf_files_controller_before_view, { file: @revision.file.download })
|
||||
return if results.first == true
|
||||
|
||||
member = Member.find_by(user_id: User.current.id, project_id: @file.project.id)
|
||||
@ -89,7 +89,7 @@ class DmsfFilesController < ApplicationController
|
||||
params[:disposition] = 'attachment' if params[:filename].present?
|
||||
send_data @revision.file.download,
|
||||
filename: filename,
|
||||
type: @revision.detect_content_type,
|
||||
type: @revision.content_type,
|
||||
disposition: params[:disposition].presence || @revision.dmsf_file.disposition
|
||||
end
|
||||
rescue DmsfAccessError => e
|
||||
@ -139,21 +139,18 @@ class DmsfFilesController < ApplicationController
|
||||
upload = DmsfUpload.create_from_uploaded_attachment(@project, @folder, file_upload)
|
||||
if upload
|
||||
revision.size = upload.size
|
||||
revision.disk_filename = revision.new_storage_filename
|
||||
revision.file.attach(
|
||||
io: File.open(upload.tempfile_path),
|
||||
filename: revision.disk_filename,
|
||||
content_type: revision.mime_type,
|
||||
filename: file_upload.filename,
|
||||
content_type: Redmine::MimeType.of(file_upload.filename),
|
||||
identify: false
|
||||
)
|
||||
end
|
||||
else
|
||||
revision.size = last_revision.size
|
||||
revision.disk_filename = last_revision.disk_filename
|
||||
end
|
||||
# Custom fields
|
||||
revision.copy_custom_field_values(params[:dmsf_file_revision][:custom_field_values], last_revision)
|
||||
@file.name = revision.name
|
||||
ok = true
|
||||
if revision.save
|
||||
revision.assign_workflow params[:dmsf_workflow_id]
|
||||
@ -330,8 +327,8 @@ class DmsfFilesController < ApplicationController
|
||||
if tbnail
|
||||
if stale?(etag: tbnail)
|
||||
send_file tbnail,
|
||||
filename: filename_for_content_disposition(@file.last_revision.disk_file),
|
||||
type: @file.last_revision.detect_content_type,
|
||||
filename: filename_for_content_disposition(@file.name),
|
||||
type: @file.last_revision.content_type,
|
||||
disposition: 'inline'
|
||||
end
|
||||
else
|
||||
|
||||
@ -32,7 +32,7 @@ class DmsfPublicUrlsController < ApplicationController
|
||||
expires_in 0.years, 'must-revalidate' => true
|
||||
send_data(revision.file.download,
|
||||
filename: filename_for_content_disposition(revision.name),
|
||||
type: revision.detect_content_type,
|
||||
type: revision.content_type,
|
||||
disposition: dmsf_public_url.dmsf_file.disposition)
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
|
||||
@ -42,7 +42,6 @@ module DmsfUploadHelper
|
||||
else
|
||||
file = DmsfFile.new
|
||||
file.project_id = project.id
|
||||
file.name = name
|
||||
file.dmsf_folder = folder
|
||||
file.notification = RedmineDmsf.dmsf_default_notifications?
|
||||
end
|
||||
@ -84,8 +83,6 @@ module DmsfUploadHelper
|
||||
next
|
||||
end
|
||||
|
||||
new_revision.disk_filename = new_revision.new_storage_filename
|
||||
|
||||
if new_revision.save
|
||||
new_revision.assign_workflow committed_file[:dmsf_workflow_id]
|
||||
begin
|
||||
@ -97,7 +94,7 @@ module DmsfUploadHelper
|
||||
new_revision.file.attach(
|
||||
io: File.open(committed_file[:tempfile_path]),
|
||||
filename: new_revision.name,
|
||||
content_type: new_revision.mime_type,
|
||||
content_type: new_revision.content_type,
|
||||
identify: false
|
||||
)
|
||||
file.last_revision = new_revision
|
||||
|
||||
@ -42,15 +42,6 @@ class DmsfFile < ApplicationRecord
|
||||
scope :visible, -> { where(deleted: STATUS_ACTIVE) }
|
||||
scope :deleted, -> { where(deleted: STATUS_DELETED) }
|
||||
|
||||
validates :name, dmsf_file_name: true
|
||||
validates :name, length: { maximum: 255 }
|
||||
validates :name,
|
||||
uniqueness: {
|
||||
scope: %i[dmsf_folder_id project_id deleted],
|
||||
conditions: -> { where(deleted: STATUS_ACTIVE) },
|
||||
case_sensitive: true
|
||||
}
|
||||
|
||||
acts_as_event(
|
||||
title: proc { |o|
|
||||
@searched_revision = nil
|
||||
@ -81,7 +72,7 @@ class DmsfFile < ApplicationRecord
|
||||
url: proc { |o|
|
||||
if @searched_revision
|
||||
{ controller: 'dmsf_files', action: 'view', id: o.id, download: @searched_revision.id,
|
||||
filename: o.name }
|
||||
filename: @searched_revision.name }
|
||||
else
|
||||
{ controller: 'dmsf_files', action: 'view', id: o.id, filename: o.name }
|
||||
end
|
||||
@ -104,7 +95,7 @@ class DmsfFile < ApplicationRecord
|
||||
acts_as_watchable
|
||||
acts_as_searchable(
|
||||
columns: [
|
||||
"#{table_name}.name",
|
||||
"#{DmsfFileRevision.table_name}.name",
|
||||
"#{DmsfFileRevision.table_name}.title",
|
||||
"#{DmsfFileRevision.table_name}.description",
|
||||
"#{DmsfFileRevision.table_name}.comment"
|
||||
@ -143,11 +134,19 @@ class DmsfFile < ApplicationRecord
|
||||
end
|
||||
|
||||
def self.find_file_by_name(project, folder, name)
|
||||
findn_file_by_name project&.id, folder, name
|
||||
dmsf_files = visible.where(dmsf_files: { project_id: project&.id, dmsf_folder_id: folder&.id })
|
||||
dmsf_files.each do |file|
|
||||
return file if file.name == name
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def self.findn_file_by_name(project_id, folder, name)
|
||||
visible.find_by project_id: project_id, dmsf_folder_id: folder&.id, name: name
|
||||
def self.find_file_by_title(project, folder, name)
|
||||
dmsf_files = visible.where(dmsf_files: { project_id: project&.id, dmsf_folder_id: folder&.id })
|
||||
dmsf_files.each do |file|
|
||||
return file if file.title == name
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def approval_allowed_zero_minor
|
||||
@ -155,10 +154,7 @@ class DmsfFile < ApplicationRecord
|
||||
end
|
||||
|
||||
def last_revision
|
||||
unless defined?(@last_revision)
|
||||
@last_revision = deleted? ? dmsf_file_revisions.first : dmsf_file_revisions.visible.first
|
||||
end
|
||||
@last_revision
|
||||
@last_revision ||= deleted? ? dmsf_file_revisions.first : dmsf_file_revisions.visible.first
|
||||
end
|
||||
|
||||
def deleted?
|
||||
@ -211,16 +207,20 @@ class DmsfFile < ApplicationRecord
|
||||
save
|
||||
end
|
||||
|
||||
def name
|
||||
last_revision&.name.to_s
|
||||
end
|
||||
|
||||
def title
|
||||
last_revision ? last_revision.title : name
|
||||
last_revision&.title.to_s
|
||||
end
|
||||
|
||||
def description
|
||||
last_revision ? last_revision.description : ''
|
||||
last_revision&.description.to_s
|
||||
end
|
||||
|
||||
def version
|
||||
last_revision ? last_revision.version : '0'
|
||||
last_revision&.version.to_s
|
||||
end
|
||||
|
||||
def workflow
|
||||
@ -228,7 +228,7 @@ class DmsfFile < ApplicationRecord
|
||||
end
|
||||
|
||||
def size
|
||||
last_revision ? last_revision.size : 0
|
||||
last_revision&.size.to_i
|
||||
end
|
||||
|
||||
def dmsf_path
|
||||
@ -313,28 +313,12 @@ class DmsfFile < ApplicationRecord
|
||||
file = DmsfFile.new
|
||||
file.dmsf_folder_id = folder.id if folder
|
||||
file.project_id = project.id
|
||||
title = last_revision&.title
|
||||
if DmsfFile.visible.exists?(project_id: file.project_id, dmsf_folder_id: file.dmsf_folder_id, name: filename)
|
||||
basename = File.basename(filename, '.*')
|
||||
extname = File.extname(filename)
|
||||
1.step do |i|
|
||||
title = "#{basename} (#{i})"
|
||||
gen_filename = "#{title}#{extname}"
|
||||
unless DmsfFile.visible.exists?(project_id: file.project_id, dmsf_folder_id: file.dmsf_folder_id,
|
||||
name: gen_filename)
|
||||
filename = gen_filename
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
file.name = filename
|
||||
file.notification = RedmineDmsf.dmsf_default_notifications?
|
||||
if file.save && last_revision
|
||||
new_revision = last_revision.clone
|
||||
new_revision.name = filename
|
||||
new_revision.title = title
|
||||
new_revision.title = File.basename(filename, '.*')
|
||||
new_revision.dmsf_file = file
|
||||
new_revision.disk_filename = new_revision.new_storage_filename
|
||||
# Assign the same workflow if it's a global one, or we are in the same project
|
||||
new_revision.workflow = nil
|
||||
new_revision.dmsf_workflow_id = nil
|
||||
@ -350,7 +334,7 @@ class DmsfFile < ApplicationRecord
|
||||
if last_revision.file.attached?
|
||||
begin
|
||||
new_revision.file.attach(
|
||||
io: StringIO.new(last_revision.file.blob.download),
|
||||
io: StringIO.new(last_revision.file.download),
|
||||
filename: filename,
|
||||
content_type: new_revision.file.content_type,
|
||||
identify: false
|
||||
@ -367,10 +351,18 @@ class DmsfFile < ApplicationRecord
|
||||
v.value = cv.value
|
||||
new_revision.custom_values << v
|
||||
end
|
||||
# Check the name and title
|
||||
basename = File.basename(filename, '.*')
|
||||
extname = File.extname(filename)
|
||||
i = 1
|
||||
while new_revision.invalid? && i < 1_000
|
||||
new_revision.title = "#{basename} (#{i})"
|
||||
new_revision.name = "#{new_revision.title}#{extname}"
|
||||
i += 1
|
||||
end
|
||||
if new_revision.save
|
||||
file.last_revision = new_revision
|
||||
else
|
||||
errors.add :base, new_revision.errors.full_messages.to_sentence
|
||||
Rails.logger.error new_revision.errors.full_messages.to_sentence
|
||||
file.delete commit: true
|
||||
file = nil
|
||||
@ -504,29 +496,34 @@ class DmsfFile < ApplicationRecord
|
||||
end
|
||||
|
||||
def text?
|
||||
filename = last_revision&.disk_filename
|
||||
Redmine::MimeType.is_type?('text', filename) ||
|
||||
Redmine::SyntaxHighlighting.filename_supported?(filename)
|
||||
return false unless last_revision
|
||||
|
||||
filename = last_revision.file&.blob&.filename.to_s
|
||||
last_revision.file&.blob&.text? || Redmine::SyntaxHighlighting.filename_supported?(filename)
|
||||
end
|
||||
|
||||
def image?
|
||||
Redmine::MimeType.is_type?('image', last_revision&.disk_filename)
|
||||
last_revision && last_revision.file&.blob&.image?
|
||||
end
|
||||
|
||||
def pdf?
|
||||
Redmine::MimeType.of(last_revision&.disk_filename) == 'application/pdf'
|
||||
last_revision&.content_type == 'application/pdf'
|
||||
end
|
||||
|
||||
def video?
|
||||
Redmine::MimeType.is_type?('video', last_revision&.disk_filename)
|
||||
return false unless last_revision
|
||||
|
||||
Redmine::MimeType.is_type?('video', last_revision.file.blob&.filename&.to_s)
|
||||
end
|
||||
|
||||
def html?
|
||||
Redmine::MimeType.of(last_revision&.disk_filename) == 'text/html'
|
||||
last_revision&.content_type == 'text/html'
|
||||
end
|
||||
|
||||
def office_doc?
|
||||
case File.extname(last_revision&.disk_filename)
|
||||
return false unless last_revision
|
||||
|
||||
case File.extname(last_revision.file.blob&.filename&.to_s)
|
||||
when '.odt', '.ods', '.odp', '.odg', # LibreOffice
|
||||
'.doc', '.docx', '.docm', '.xls', '.xlsx', '.xlsm', '.ppt', '.pptx', '.pptm', # MS Office
|
||||
'.rtf' # Universal
|
||||
@ -537,11 +534,11 @@ class DmsfFile < ApplicationRecord
|
||||
end
|
||||
|
||||
def markdown?
|
||||
Redmine::MimeType.of(last_revision&.disk_filename) == 'text/markdown'
|
||||
last_revision&.content_type == 'text/markdown'
|
||||
end
|
||||
|
||||
def textile?
|
||||
Redmine::MimeType.of(last_revision&.disk_filename) == 'text/x-textile'
|
||||
last_revision&.content_type == 'text/textile'
|
||||
end
|
||||
|
||||
def disposition
|
||||
@ -573,8 +570,7 @@ class DmsfFile < ApplicationRecord
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error do
|
||||
%(An error occurred while generating preview for #{last_revision.file.name} to #{target}\n
|
||||
Exception was: #{e.message})
|
||||
%(An error occurred while generating preview for #{name} to #{target}\nException was: #{e.message})
|
||||
end
|
||||
''
|
||||
end
|
||||
@ -604,11 +600,7 @@ class DmsfFile < ApplicationRecord
|
||||
end
|
||||
|
||||
def formatted_name(member)
|
||||
if last_revision
|
||||
last_revision.formatted_name(member)
|
||||
else
|
||||
name
|
||||
end
|
||||
last_revision&.formatted_name(member)
|
||||
end
|
||||
|
||||
def owner?(user)
|
||||
@ -640,10 +632,6 @@ class DmsfFile < ApplicationRecord
|
||||
nil
|
||||
end
|
||||
|
||||
def extension
|
||||
File.extname(last_revision.disk_filename).strip.downcase[1..] if last_revision
|
||||
end
|
||||
|
||||
def thumbnail(options = {})
|
||||
size = options[:size].to_i
|
||||
if size.positive?
|
||||
@ -657,10 +645,10 @@ class DmsfFile < ApplicationRecord
|
||||
size = 100 unless size.positive?
|
||||
target = File.join(Attachment.thumbnails_storage_path, "#{id}_#{last_revision.digest}_#{size}.thumb")
|
||||
begin
|
||||
Redmine::Thumbnail.generate last_revision.disk_file.to_s, target, size, pdf?
|
||||
Redmine::Thumbnail.generate last_revision.file.download, target, size, pdf?
|
||||
rescue StandardError => e
|
||||
Rails.logger.error do
|
||||
%(An error occured while generating thumbnail for #{last_revision.disk_file} to #{target}\n
|
||||
%(An error occured while generating thumbnail for #{last_revision.file&.blob&.filename} to #{target}\n
|
||||
Exception was: #{e.message})
|
||||
end
|
||||
nil
|
||||
|
||||
@ -90,13 +90,9 @@ class DmsfFileRevision < ApplicationRecord
|
||||
}
|
||||
)
|
||||
|
||||
validates :title, presence: true
|
||||
validates :title, length: { maximum: 255 }
|
||||
validates :title, presence: true, dmsf_file_name: true, length: { maximum: 255 }
|
||||
validates :major_version, presence: true
|
||||
validates :name, dmsf_file_name: true
|
||||
validates :name, length: { maximum: 255 }
|
||||
validates :disk_filename, length: { maximum: 255 }
|
||||
validates :name, dmsf_file_extension: true
|
||||
validates :name, presence: true, dmsf_file_name: true, length: { maximum: 255 }, dmsf_file_extension: true
|
||||
validates :description, length: { maximum: 1.kilobyte }
|
||||
validates :size, dmsf_max_file_size: true
|
||||
|
||||
@ -118,8 +114,11 @@ class DmsfFileRevision < ApplicationRecord
|
||||
file.blob&.checksum
|
||||
end
|
||||
|
||||
def mime_type
|
||||
file.blob&.content_type
|
||||
def content_type
|
||||
res = file.blob&.content_type
|
||||
res = Redmine::MimeType.of(file.blob&.filename) if res.blank?
|
||||
res = 'application/octet-stream' if res.blank?
|
||||
res
|
||||
end
|
||||
|
||||
def visible?(_user = nil)
|
||||
@ -191,49 +190,9 @@ class DmsfFileRevision < ApplicationRecord
|
||||
ver
|
||||
end
|
||||
|
||||
def storage_base_path
|
||||
time = created_at || DateTime.current
|
||||
DmsfFile.storage_path.join(time.strftime('%Y')).join time.strftime('%m')
|
||||
end
|
||||
|
||||
def disk_file(search_if_not_exists: true)
|
||||
path = storage_base_path
|
||||
begin
|
||||
FileUtils.mkdir_p(path)
|
||||
rescue StandardError => e
|
||||
Rails.logger.error e.message
|
||||
end
|
||||
filename = path.join(disk_filename)
|
||||
if search_if_not_exists && !File.exist?(filename)
|
||||
# Let's search for the physical file in source revisions
|
||||
dmsf_file.dmsf_file_revisions.where(created_at: ...created_at).order(created_at: :desc).each do |rev|
|
||||
filename = rev.disk_file
|
||||
break if File.exist?(filename)
|
||||
end
|
||||
end
|
||||
filename.to_s
|
||||
end
|
||||
|
||||
def new_storage_filename
|
||||
raise DmsfAccessError, 'File id is not set' unless dmsf_file&.id
|
||||
|
||||
filename = DmsfHelper.sanitize_filename(name)
|
||||
timestamp = DateTime.current.strftime('%y%m%d%H%M%S')
|
||||
timestamp.succ! while File.exist? storage_base_path.join("#{timestamp}_#{dmsf_file.id}_#{filename}")
|
||||
"#{timestamp}_#{dmsf_file.id}_#{filename}"
|
||||
end
|
||||
|
||||
def detect_content_type
|
||||
content_type = mime_type
|
||||
content_type = Redmine::MimeType.of(disk_filename) if content_type.blank?
|
||||
content_type = 'application/octet-stream' if content_type.blank?
|
||||
content_type
|
||||
end
|
||||
|
||||
def clone
|
||||
new_revision = DmsfFileRevision.new
|
||||
new_revision.dmsf_file = dmsf_file
|
||||
new_revision.disk_filename = disk_filename
|
||||
new_revision.size = size
|
||||
new_revision.title = title
|
||||
new_revision.description = description
|
||||
@ -325,7 +284,7 @@ class DmsfFileRevision < ApplicationRecord
|
||||
file.attach(
|
||||
io: open_file,
|
||||
filename: dmsf_file.name,
|
||||
content_type: mime_type.presence || Redmine::MimeType.of(disk_filename),
|
||||
content_type: content_type,
|
||||
identify: false
|
||||
)
|
||||
end
|
||||
@ -396,7 +355,7 @@ class DmsfFileRevision < ApplicationRecord
|
||||
end
|
||||
|
||||
def protocol
|
||||
@protocol ||= PROTOCOLS[mime_type.downcase] if mime_type
|
||||
@protocol ||= PROTOCOLS[content_type.downcase] if content_type.present?
|
||||
@protocol
|
||||
end
|
||||
|
||||
|
||||
@ -28,9 +28,9 @@ class DmsfFolder < ApplicationRecord
|
||||
belongs_to :deleted_by_user, class_name: 'User'
|
||||
belongs_to :user
|
||||
|
||||
has_many :dmsf_folders, -> { order :title }, dependent: :destroy, inverse_of: :dmsf_folder
|
||||
has_many :dmsf_folders, dependent: :destroy, inverse_of: :dmsf_folder
|
||||
has_many :dmsf_files, dependent: :destroy
|
||||
has_many :folder_links, -> { where(target_type: 'DmsfFolder').order(:name) },
|
||||
has_many :folder_links, -> { where(target_type: 'DmsfFolder') },
|
||||
class_name: 'DmsfLink', foreign_key: 'dmsf_folder_id', dependent: :destroy, inverse_of: :dmsf_folder
|
||||
has_many :file_links, -> { where(target_type: 'DmsfFile') },
|
||||
class_name: 'DmsfLink', foreign_key: 'dmsf_folder_id', dependent: :destroy, inverse_of: :dmsf_folder
|
||||
@ -91,7 +91,7 @@ class DmsfFolder < ApplicationRecord
|
||||
datetime: proc { |o| o.updated_at },
|
||||
author: proc { |o| o.user }
|
||||
|
||||
validates :title, presence: true, dmsf_file_name: true
|
||||
validates :title, presence: true, length: { maximum: 255 }, dmsf_folder_name: true
|
||||
validates :title, uniqueness: { scope: %i[dmsf_folder_id project_id deleted],
|
||||
conditions: -> { where(deleted: STATUS_ACTIVE) }, case_sensitive: true }
|
||||
validates :description, length: { maximum: 65_535 }
|
||||
|
||||
@ -26,7 +26,9 @@ class DmsfLink < ApplicationRecord
|
||||
belongs_to :deleted_by_user, class_name: 'User'
|
||||
belongs_to :user
|
||||
|
||||
validates :name, presence: true, length: { maximum: 255 }
|
||||
validates :name, presence: true, length: { maximum: 255 }, dmsf_link_name: true
|
||||
validates :name, uniqueness: { scope: %i[dmsf_folder_id project_id deleted],
|
||||
conditions: -> { where(deleted: STATUS_ACTIVE) }, case_sensitive: true }
|
||||
# There can be project_id = -1 when attaching links to an issue. The project_id is assigned later when saving the
|
||||
# issue.
|
||||
validates :external_url, length: { maximum: 255 }
|
||||
|
||||
@ -22,6 +22,38 @@ class DmsfFileNameValidator < ActiveModel::EachValidator
|
||||
ALL_INVALID_CHARACTERS = /\A[^#{DmsfFolder::INVALID_CHARACTERS}]*\z/
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
# Check invalid characters
|
||||
record.errors.add attribute, :error_contains_invalid_character unless ALL_INVALID_CHARACTERS.match?(value)
|
||||
|
||||
# Check name uniqueness among files
|
||||
project_id = record.dmsf_file.project_id
|
||||
dmsf_folder_id = record.dmsf_file.dmsf_folder_id
|
||||
id = record.dmsf_file_id
|
||||
DmsfFile
|
||||
.visible
|
||||
.where(project_id: project_id, dmsf_folder_id: dmsf_folder_id)
|
||||
.where.not(id: id)
|
||||
.find_each do |file|
|
||||
if file.name == value || file.title == value
|
||||
record.errors.add attribute, :taken
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Check name uniqueness among folders
|
||||
DmsfFolder.visible.where(project_id: project_id, dmsf_folder_id: dmsf_folder_id).find_each do |folder|
|
||||
if folder.title == value
|
||||
record.errors.add attribute, :taken
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Check name uniqueness among links
|
||||
DmsfLink.visible.where(project_id: project_id, dmsf_folder_id: dmsf_folder_id).find_each do |link|
|
||||
if link.name == value
|
||||
record.errors.add attribute, :taken
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
44
app/validators/dmsf_folder_name_validator.rb
Normal file
44
app/validators/dmsf_folder_name_validator.rb
Normal file
@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Vít Jonáš <vit.jonas@gmail.com>, Karel Pičman <karel.picman@kontron.com>
|
||||
#
|
||||
# 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
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
|
||||
# File name validator
|
||||
class DmsfFolderNameValidator < ActiveModel::EachValidator
|
||||
ALL_INVALID_CHARACTERS = /\A[^#{DmsfFolder::INVALID_CHARACTERS}]*\z/
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
# Check invalid characters
|
||||
record.errors.add attribute, :error_contains_invalid_character unless ALL_INVALID_CHARACTERS.match?(value)
|
||||
|
||||
# Check name uniqueness among files
|
||||
DmsfFile.visible.where(project_id: record.project_id, dmsf_folder_id: record.dmsf_folder_id).find_each do |file|
|
||||
if file.name == value || file.title == value
|
||||
record.errors.add attribute, :taken
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Check name uniqueness among links
|
||||
DmsfLink.visible.where(project_id: record.project_id, dmsf_folder_id: record.dmsf_folder_id).find_each do |link|
|
||||
if link.name == value
|
||||
record.errors.add attribute, :taken
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
44
app/validators/dmsf_link_name_validator.rb
Normal file
44
app/validators/dmsf_link_name_validator.rb
Normal file
@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Vít Jonáš <vit.jonas@gmail.com>, Karel Pičman <karel.picman@kontron.com>
|
||||
#
|
||||
# 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
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
|
||||
# File name validator
|
||||
class DmsfLinkNameValidator < ActiveModel::EachValidator
|
||||
ALL_INVALID_CHARACTERS = /\A[^#{DmsfFolder::INVALID_CHARACTERS}]*\z/
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
# Check invalid characters
|
||||
record.errors.add attribute, :error_contains_invalid_character unless ALL_INVALID_CHARACTERS.match?(value)
|
||||
|
||||
# Check name uniqueness among files
|
||||
DmsfFile.visible.where(project_id: record.project_id, dmsf_folder_id: record.dmsf_folder_id).find_each do |file|
|
||||
if file.name == value || file.title == value
|
||||
record.errors.add attribute, :taken
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Check name uniqueness among folders
|
||||
DmsfFolder.visible.where(project_id: record.project_id, dmsf_folder_id: record.dmsf_folder_id).find_each do |folder|
|
||||
if folder.title == value
|
||||
record.errors.add attribute, :taken
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -71,7 +71,7 @@
|
||||
<% member = Member.find_by(user_id: User.current.id, project_id: dmsf_file.project.id) %>
|
||||
<% filename = dmsf_file.last_revision&.formatted_name(member) %>
|
||||
<%= context_menu_link sprite_icon('download', l(:button_download)),
|
||||
static_dmsf_file_path(dmsf_file, filename: filename),
|
||||
static_dmsf_file_path(dmsf_file, filename: filename, download: dmsf_file.last_revision&.id),
|
||||
class: 'icon icon-download', data: { cy: "icon__download--dmsf_file_#{dmsf_file.id}" },
|
||||
disabled: false %>
|
||||
</li>
|
||||
|
||||
@ -40,7 +40,8 @@
|
||||
<% member = Member.find_by(user_id: User.current.id, project_id: file.project.id) %>
|
||||
<% filename = file.last_revision&.formatted_name(member) %>
|
||||
<%= link_to sprite_icon('download', l(:button_download)),
|
||||
static_dmsf_file_path(file, filename: filename), class: 'icon icon-download', disabled: false %>
|
||||
static_dmsf_file_path(file, filename: filename, download: file.last_revision&.id),
|
||||
class: 'icon icon-download', disabled: false %>
|
||||
<%= render partial: 'dmsf_context_menus/watch', locals: { object: file } %>
|
||||
<%= delete_link(dmsf_file_path(id: file, details: true),
|
||||
back_url: dmsf_folder_path(id: file.project, folder_id: file.dmsf_folder)) if file_delete_allowed %>
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
title: h(dmsf_file.last_revision.try(:tooltip)),
|
||||
'data-downloadurl' => "#{dmsf_file.last_revision.detect_content_type}:#{h(dmsf_file.name)}:#{file_view_url}" %>
|
||||
'data-downloadurl' => "#{dmsf_file.last_revision.content_type}:#{h(dmsf_file.name)}:#{file_view_url}" %>
|
||||
</td>
|
||||
<td class="<%= cls %>">
|
||||
<span class="size">(<%= number_to_human_size dmsf_file.last_revision.size %>)</span>
|
||||
|
||||
@ -14,7 +14,7 @@ api.dmsf_file do
|
||||
api.dmsf_string "{{dmsf(#{@file.id},#{@file.name},#{r.id})}}"
|
||||
api.content_url view_dmsf_file_url(@file, download: r)
|
||||
api.size r.size
|
||||
api.mime_type r.mime_type
|
||||
api.mime_type r.content_type
|
||||
api.title r.title
|
||||
api.description r.description
|
||||
api.workflow r.workflow
|
||||
|
||||
@ -133,7 +133,7 @@
|
||||
</div>
|
||||
<div class="status attribute">
|
||||
<%= content_tag :div, l(:label_mime), class: 'label' %>
|
||||
<%= content_tag :div, revision.mime_type, class: 'value' %>
|
||||
<%= content_tag :div, revision.content_type, class: 'value' %>
|
||||
</div>
|
||||
<% if revision.checksum.present? %>
|
||||
<div class="status attribute">
|
||||
|
||||
@ -19,7 +19,8 @@
|
||||
|
||||
<div class="contextual">
|
||||
<%= link_to "#{l(:button_download)} (#{number_to_human_size(@file.size)})",
|
||||
static_dmsf_file_path(@file, download: @file.last_revision, filename: @file.last_revision.disk_filename),
|
||||
static_dmsf_file_path(@file, download: @file.last_revision,
|
||||
filename: @file.last_revision.file&.blob&.filename),
|
||||
class: 'icon icon-download', disabled: false %>
|
||||
</div>
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ class ActiveStorageMigration < ActiveRecord::Migration[7.0]
|
||||
r.file.attach(
|
||||
io: File.open(path),
|
||||
filename: r.name,
|
||||
content_type: r.mime_type,
|
||||
content_type: r.content_type,
|
||||
identify: false
|
||||
)
|
||||
# Remove the original file
|
||||
@ -60,18 +60,51 @@ class ActiveStorageMigration < ActiveRecord::Migration[7.0]
|
||||
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 = r.disk_file(search_if_not_exists: false)
|
||||
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}"
|
||||
@ -100,4 +133,19 @@ class ActiveStorageMigration < ActiveRecord::Migration[7.0]
|
||||
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
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Karel Pičman <karel.picman@kontron.com>
|
||||
#
|
||||
# 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
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Restore DmsfFileRevision.updated_at from DmsfFileRevision.temp_updated_at column
|
||||
class RestoreUpdatedAt < ActiveRecord::Migration[7.0]
|
||||
# temp_updated_at => updated_at
|
||||
def up
|
||||
DmsfFileRevision.update_all 'updated_at = temp_updated_at'
|
||||
remove_column :dmsf_file_revisions, :temp_updated_at
|
||||
end
|
||||
end
|
||||
@ -1,34 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Karel Pičman <karel.picman@kontron.com>
|
||||
#
|
||||
# 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
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Add column
|
||||
class RemoveDuplicitiesFromRevision < ActiveRecord::Migration[7.0]
|
||||
def up
|
||||
remove_column :dmsf_file_revisions, :digest
|
||||
remove_column :dmsf_file_revisions, :mime_type
|
||||
# We need to keep the size despite the fact that it's duplicated in active_storage_blobs to speed up the main
|
||||
# document view
|
||||
end
|
||||
|
||||
def down
|
||||
add_column :dmsf_file_revisions, :digest, :string, limit: 64, default: '', null: false
|
||||
add_column :dmsf_file_revisions, :mime_type, :string
|
||||
# Recalculation of these columns for all revisions is technically possible but costs are too high.
|
||||
end
|
||||
end
|
||||
@ -91,7 +91,7 @@ module RedmineDmsf
|
||||
rel: 'noopener',
|
||||
class: 'icon icon-file',
|
||||
title: h(revision.try(:tooltip)),
|
||||
'data-downloadurl' => "#{revision.detect_content_type}:#{h(revision.dmsf_file.name)}:#{file_view_url}"
|
||||
'data-downloadurl' => "#{revision.content_type}:#{h(revision.dmsf_file.name)}:#{file_view_url}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -49,7 +49,7 @@ module RedmineDmsf
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
title: h(revision.tooltip),
|
||||
'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{url}"
|
||||
'data-downloadurl' => "#{file.last_revision.content_type}:#{h(file.name)}:#{url}"
|
||||
end
|
||||
|
||||
# dmsff - link to a folder
|
||||
@ -278,7 +278,7 @@ module RedmineDmsf
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
title: h(file.last_revision.try(:tooltip)),
|
||||
'data-downloadurl' => "#{file.last_revision.detect_content_type}:#{h(file.name)}:#{url}")
|
||||
'data-downloadurl' => "#{file.last_revision.content_type}:#{h(file.name)}:#{url}")
|
||||
end
|
||||
safe_join html
|
||||
end
|
||||
|
||||
@ -31,7 +31,7 @@ module RedmineDmsf
|
||||
def get_image_filename(attrname)
|
||||
if attrname =~ %r{/dmsf/files/(\d+)/}
|
||||
file = DmsfFile.find_by(id: Regexp.last_match(1))
|
||||
file&.last_revision&.disk_file
|
||||
file.last_revision.file&.blob&.filename if file&.last_revision
|
||||
else
|
||||
super
|
||||
end
|
||||
|
||||
@ -49,9 +49,9 @@ module RedmineDmsf
|
||||
# New methods
|
||||
def self.prepended(base)
|
||||
base.class_eval do
|
||||
has_many :dmsf_files, -> { where(dmsf_folder_id: nil).order(:name) },
|
||||
has_many :dmsf_files, -> { where(dmsf_folder_id: nil) },
|
||||
class_name: 'DmsfFile', foreign_key: 'project_id', dependent: :destroy
|
||||
has_many :dmsf_folders, -> { where(dmsf_folder_id: nil).order(:title) },
|
||||
has_many :dmsf_folders, -> { where(dmsf_folder_id: nil) },
|
||||
class_name: 'DmsfFolder', foreign_key: 'project_id', dependent: :destroy
|
||||
has_many :dmsf_workflows, dependent: :destroy
|
||||
has_many :folder_links, -> { where dmsf_folder_id: nil, target_type: 'DmsfFolder' },
|
||||
|
||||
@ -55,7 +55,7 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
# Gather collection of objects that denote current entities child entities
|
||||
# Used for listing directories etc, implemented basic caching because otherwise
|
||||
# Used for listing directories etc., implemented basic caching because otherwise
|
||||
# Our already quite heavy usage of DB would just get silly every time we called
|
||||
# this method.
|
||||
def children
|
||||
@ -63,12 +63,12 @@ module RedmineDmsf
|
||||
@children = []
|
||||
if folder
|
||||
# Folders
|
||||
folder.dmsf_folders.visible.each do |f|
|
||||
@children.push child(f.title) if DmsfFolder.permissions?(f, allow_system: false)
|
||||
folder.dmsf_folders.visible.each do |folder|
|
||||
@children.push child(folder.title) if DmsfFolder.permissions?(folder, allow_system: false)
|
||||
end
|
||||
# Files
|
||||
folder.dmsf_files.visible.pluck(:name).each do |name|
|
||||
@children.push child(name)
|
||||
folder.dmsf_files.visible.each do |file|
|
||||
@children.push child(file.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -92,7 +92,7 @@ module RedmineDmsf
|
||||
def content_type
|
||||
if file
|
||||
if file.last_revision
|
||||
file.last_revision.detect_content_type
|
||||
file.last_revision.content_type
|
||||
else
|
||||
'application/octet-stream'
|
||||
end
|
||||
@ -126,13 +126,7 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
def etag
|
||||
ino = if file&.last_revision && File.exist?(file.last_revision.disk_file)
|
||||
File.stat(file.last_revision.disk_file).ino
|
||||
else
|
||||
2
|
||||
end
|
||||
format '%<node>x-%<size>x-%<modified>x',
|
||||
node: ino, size: content_length, modified: (last_modified ? last_modified.to_i : 0)
|
||||
format '%<node>x-%<size>x-%<modified>x', node: 0, size: content_length, modified: last_modified.to_i
|
||||
end
|
||||
|
||||
def content_length
|
||||
@ -272,10 +266,8 @@ module RedmineDmsf
|
||||
new_revision = dest.resource.file.last_revision.clone
|
||||
new_revision.increase_version DmsfFileRevision::PATCH_VERSION
|
||||
end
|
||||
# The file on disk must be renamed from .tmp to the correct filetype or else Xapian won't know how to index.
|
||||
# Copy file.last_revision.disk_file to new_revision.disk_file
|
||||
# Copy the file
|
||||
new_revision.size = file.last_revision.size
|
||||
new_revision.disk_filename = new_revision.new_storage_filename
|
||||
new_revision.copy_file_content StringIO.new(file.last_revision.file.download)
|
||||
# Save
|
||||
new_revision.save && dest.resource.file.save
|
||||
@ -304,7 +296,6 @@ module RedmineDmsf
|
||||
file.last_revision.name = dest.resource.basename
|
||||
file.last_revision.title = DmsfFileRevision.filename_to_title(dest.resource.basename)
|
||||
end
|
||||
file.name = dest.resource.basename
|
||||
# Save Changes
|
||||
if file.last_revision.save && file.save
|
||||
dest.exist? ? NoContent : Created
|
||||
@ -376,7 +367,6 @@ module RedmineDmsf
|
||||
|
||||
# Update Revision and names of file (We can link to old physical resource, as it's not changed)
|
||||
new_file.last_revision.name = dest.resource.basename
|
||||
new_file.name = dest.resource.basename
|
||||
# Save Changes
|
||||
unless new_file.last_revision.save && new_file.save
|
||||
new_file.delete commit: true
|
||||
@ -587,7 +577,6 @@ module RedmineDmsf
|
||||
else
|
||||
f = DmsfFile.new
|
||||
f.project_id = project.id
|
||||
f.name = basename
|
||||
f.dmsf_folder = parent.folder
|
||||
f.notification = RedmineDmsf.dmsf_default_notifications?
|
||||
new_revision = DmsfFileRevision.new
|
||||
@ -627,7 +616,6 @@ module RedmineDmsf
|
||||
raise UnprocessableEntity
|
||||
end
|
||||
|
||||
new_revision.disk_filename = new_revision.new_storage_filename unless reuse_revision
|
||||
if new_revision.save
|
||||
if request.body.respond_to?(:rewind)
|
||||
new_revision.copy_file_content request.body
|
||||
@ -775,7 +763,6 @@ module RedmineDmsf
|
||||
def create_empty_file
|
||||
f = DmsfFile.new
|
||||
f.project_id = project.id
|
||||
f.name = basename
|
||||
f.dmsf_folder = parent.folder
|
||||
if f.save(validate: false) # Skip validation due to invalid characters in the filename
|
||||
r = DmsfFileRevision.new
|
||||
@ -786,14 +773,18 @@ module RedmineDmsf
|
||||
r.user = User.current
|
||||
r.name = basename
|
||||
r.size = 0
|
||||
r.disk_filename = r.new_storage_filename
|
||||
r.available_custom_fields.each do |cf| # Add default value for CFs not existing
|
||||
next unless cf.default_value
|
||||
|
||||
r.custom_field_values << CustomValue.new({ custom_field: cf, value: cf.default_value })
|
||||
end
|
||||
if r.save(validate: false) # Skip validation due to invalid characters in the filename
|
||||
FileUtils.touch r.disk_file(search_if_not_exists: false)
|
||||
revision.file.attach(
|
||||
io: File.new(upload.tempfile_path),
|
||||
filename: file_upload.filename,
|
||||
content_type: Redmine::MimeType.of(file_upload.filename),
|
||||
identify: false
|
||||
)
|
||||
return f
|
||||
end
|
||||
end
|
||||
|
||||
@ -41,8 +41,8 @@ module RedmineDmsf
|
||||
end
|
||||
# Files
|
||||
if User.current.allowed_to?(:view_dmsf_files, project)
|
||||
project.dmsf_files.visible.pluck(:name).each do |name|
|
||||
@children.push child(name)
|
||||
project.dmsf_files.visible.each do |file|
|
||||
@children.push child(file.name)
|
||||
end
|
||||
end
|
||||
@children
|
||||
|
||||
16
test/fixtures/active_storage_attachments.yml
vendored
16
test/fixtures/active_storage_attachments.yml
vendored
@ -31,14 +31,6 @@ active_storage_attachment_4:
|
||||
blob_id: 4
|
||||
created_at: <%= Time.now %>
|
||||
|
||||
#active_storage_attachment_5:
|
||||
# id: 5
|
||||
# name: 'shared_file'
|
||||
# record_type: 'DmsfFileRevision'
|
||||
# record_id: 5
|
||||
# blob_id: 5
|
||||
# created_at: <%= Time.now %>
|
||||
|
||||
active_storage_attachment_6:
|
||||
id: 6
|
||||
name: 'shared_file'
|
||||
@ -102,3 +94,11 @@ active_storage_attachment_13:
|
||||
record_id: 13
|
||||
blob_id: 13
|
||||
created_at: <%= Time.now %>
|
||||
|
||||
active_storage_attachment_14:
|
||||
id: 14
|
||||
name: 'shared_file'
|
||||
record_type: 'DmsfFileRevision'
|
||||
record_id: 14
|
||||
blob_id: 14
|
||||
created_at: <%= Time.now %>
|
||||
|
||||
11
test/fixtures/active_storage_blobs.yml
vendored
11
test/fixtures/active_storage_blobs.yml
vendored
@ -130,3 +130,14 @@ active_storage_blob_13:
|
||||
byte_size: 10179
|
||||
checksum : 'k08HeKksIVI7PXr1aEVbjg=='
|
||||
created_at: <%= Time.now %>
|
||||
|
||||
active_storage_blob_14:
|
||||
id: 14
|
||||
key: '5lge4yv88jwzt7xl76vri2be1v14'
|
||||
filename: 'test.html'
|
||||
content_type: 'text/html'
|
||||
metadata: '{"identified":true,"analyzed":true}'
|
||||
service_name: 'test'
|
||||
byte_size: 10179
|
||||
checksum : 'RV3RPuaIjvHzOXpvTLhI3w=='
|
||||
created_at: <%= Time.now %>
|
||||
|
||||
33
test/fixtures/dmsf_file_revisions.yml
vendored
33
test/fixtures/dmsf_file_revisions.yml
vendored
@ -4,7 +4,6 @@ dmsf_file_revisions_001:
|
||||
dmsf_file_id: 1
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: "test.txt"
|
||||
disk_filename: "test.txt"
|
||||
size: 3
|
||||
title: "Test File"
|
||||
description: 'Some file :-)'
|
||||
@ -25,7 +24,6 @@ dmsf_file_revisions_002:
|
||||
dmsf_file_id: 2
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: "test2.txt"
|
||||
disk_filename: "test2.txt"
|
||||
size: 3
|
||||
title: "Test File"
|
||||
description: NULL
|
||||
@ -46,7 +44,6 @@ dmsf_file_revisions_003:
|
||||
dmsf_file_id: 3
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'deleted.txt'
|
||||
disk_filename: 'deleted.txt'
|
||||
size: 3
|
||||
title: 'Test File'
|
||||
description: NULL
|
||||
@ -66,7 +63,6 @@ dmsf_file_revisions_004:
|
||||
dmsf_file_id: 4
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'test4.txt'
|
||||
disk_filename: 'test4.txt'
|
||||
size: 3
|
||||
title: 'Test File'
|
||||
description: NULL
|
||||
@ -86,7 +82,6 @@ dmsf_file_revisions_006:
|
||||
dmsf_file_id: 7
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'test.gif'
|
||||
disk_filename: 'test.gif'
|
||||
size: 310
|
||||
title: 'Test image'
|
||||
description: NULL
|
||||
@ -106,7 +101,6 @@ dmsf_file_revisions_007:
|
||||
dmsf_file_id: 8
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'test.pdf'
|
||||
disk_filename: 'test.pdf'
|
||||
size: 6942
|
||||
title: 'Test PDF'
|
||||
description: NULL
|
||||
@ -125,8 +119,7 @@ dmsf_file_revisions_008:
|
||||
id: 8
|
||||
dmsf_file_id: 9
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'myfile.txt'
|
||||
disk_filename: 'myfile.txt' # The file is not physically present
|
||||
name: 'myfile.txt' # The file is not physically present
|
||||
size: 0
|
||||
title: 'My File'
|
||||
description: NULL
|
||||
@ -146,7 +139,6 @@ dmsf_file_revisions_009:
|
||||
dmsf_file_id: 10
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'zero.txt'
|
||||
disk_filename: 'zero.txt'
|
||||
size: 0
|
||||
title: 'Zero Size File'
|
||||
description: NULL
|
||||
@ -166,7 +158,6 @@ dmsf_file_revisions_010:
|
||||
dmsf_file_id: 5
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'test.txt'
|
||||
disk_filename: 'test.txt'
|
||||
size: 4
|
||||
title: 'Test File'
|
||||
description: 'Some file :-)'
|
||||
@ -186,7 +177,6 @@ dmsf_file_revisions_011:
|
||||
dmsf_file_id: 12
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'test.txt'
|
||||
disk_filename: 'test.txt'
|
||||
size: 4
|
||||
title: 'Test File'
|
||||
description: 'Some file :-)'
|
||||
@ -206,7 +196,6 @@ dmsf_file_revisions_012:
|
||||
dmsf_file_id: 6
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'test.mp4'
|
||||
disk_filename: 'test.mp4'
|
||||
size: 4
|
||||
title: 'test video'
|
||||
description: 'A video :-)'
|
||||
@ -226,7 +215,6 @@ dmsf_file_revisions_013:
|
||||
dmsf_file_id: 13
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'test.odt'
|
||||
disk_filename: 'test.odt'
|
||||
size: 10445
|
||||
title: 'Test office document'
|
||||
description: 'LibreOffice text'
|
||||
@ -240,3 +228,22 @@ dmsf_file_revisions_013:
|
||||
dmsf_workflow_assigned_by_user_id: NULL
|
||||
dmsf_workflow_started_by_user_id: NULL
|
||||
created_at: 2017-04-01 08:54:00 +02:00
|
||||
|
||||
dmsf_file_revisions_014:
|
||||
id: 14
|
||||
dmsf_file_id: 14
|
||||
source_dmsf_file_revision_id: NULL
|
||||
name: 'test.html'
|
||||
size: 10445
|
||||
title: 'Webpage'
|
||||
description: 'HTML document'
|
||||
workflow: 0
|
||||
minor_version: 0
|
||||
major_version: 1
|
||||
comment: NULL
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
user_id: 1
|
||||
dmsf_workflow_assigned_by_user_id: NULL
|
||||
dmsf_workflow_started_by_user_id: NULL
|
||||
created_at: 2017-04-01 08:54:00 +02:00
|
||||
20
test/fixtures/dmsf_files.yml
vendored
20
test/fixtures/dmsf_files.yml
vendored
@ -3,7 +3,6 @@ dmsf_files_001:
|
||||
id: 1
|
||||
project_id: 1
|
||||
dmsf_folder_id: NULL
|
||||
name: 'test.txt'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -13,7 +12,6 @@ dmsf_files_002:
|
||||
id: 2
|
||||
project_id: 2
|
||||
dmsf_folder_id: NULL
|
||||
name: 'test.txt'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -23,7 +21,6 @@ dmsf_files_003:
|
||||
id: 3
|
||||
project_id: 1
|
||||
dmsf_folder_id: NULL
|
||||
name: 'deleted.txt'
|
||||
notification: false
|
||||
deleted: 1
|
||||
deleted_by_user_id: 1
|
||||
@ -32,7 +29,6 @@ dmsf_files_004:
|
||||
id: 4
|
||||
project_id: 1
|
||||
dmsf_folder_id: 2
|
||||
name: 'test.txt'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -41,7 +37,6 @@ dmsf_files_005:
|
||||
id: 5
|
||||
project_id: 1
|
||||
dmsf_folder_id: 5
|
||||
name: 'test.txt'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -50,7 +45,6 @@ dmsf_files_006:
|
||||
id: 6
|
||||
project_id: 2
|
||||
dmsf_folder_id: 3
|
||||
name: 'test.mp4'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -59,7 +53,6 @@ dmsf_files_007:
|
||||
id: 7
|
||||
project_id: 1
|
||||
dmsf_folder_id: 8
|
||||
name: 'test.gif'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -68,7 +61,6 @@ dmsf_files_008:
|
||||
id: 8
|
||||
project_id: 1
|
||||
dmsf_folder_id: NULL
|
||||
name: 'test.pdf'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -77,7 +69,6 @@ dmsf_files_009:
|
||||
id: 9
|
||||
project_id: 1
|
||||
dmsf_folder_id: NULL
|
||||
name: 'myfile.txt'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -86,7 +77,6 @@ dmsf_files_010:
|
||||
id: 10
|
||||
project_id: 1
|
||||
dmsf_folder_id: NULL
|
||||
name: 'zero.txt'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -95,7 +85,6 @@ dmsf_files_011:
|
||||
id: 11
|
||||
project_id: 1
|
||||
dmsf_folder_id: 9
|
||||
name: 'zero.txt'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -104,7 +93,6 @@ dmsf_files_012:
|
||||
id: 12
|
||||
project_id: 5
|
||||
dmsf_folder_id: NULL
|
||||
name: 'test.txt'
|
||||
notification: false
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
@ -113,6 +101,12 @@ dmsf_files_013:
|
||||
id: 13
|
||||
project_id: 1
|
||||
dmsf_folder_id: NULL
|
||||
name: 'test.odt'
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
|
||||
dmsf_files_014:
|
||||
id: 14
|
||||
project_id: 1
|
||||
dmsf_folder_id: NULL
|
||||
deleted: 0
|
||||
deleted_by_user_id: NULL
|
||||
6
test/fixtures/files/5l/ge/5lge4yv88jwzt7xl76vri2be1v14
vendored
Normal file
6
test/fixtures/files/5l/ge/5lge4yv88jwzt7xl76vri2be1v14
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
Hello world!
|
||||
</body>
|
||||
</html>
|
||||
@ -142,9 +142,9 @@ class DmsfFilesControllerTest < RedmineDmsf::Test::TestCase
|
||||
version_major: @file1.last_revision.major_version,
|
||||
version_minor: @file1.last_revision.minor_version + 1,
|
||||
dmsf_file_revision: {
|
||||
title: @file1.last_revision.title,
|
||||
name: @file1.last_revision.name,
|
||||
description: @file1.last_revision.description,
|
||||
title: @file1.title,
|
||||
name: @file1.name,
|
||||
description: @file1.description,
|
||||
comment: 'New revision'
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ class DmsfFileApiTest < RedmineDmsf::Test::IntegrationTest
|
||||
# <name>test5.txt</name>
|
||||
# <content_url>http://www.example.com/dmsf/files/1/view?download=5</content_url>
|
||||
# <size>4</size>
|
||||
# <mime_type>text/plain</mime_type>
|
||||
# <content_type>text/plain</content_type>
|
||||
# <title>Test File</title>
|
||||
# <description/>
|
||||
# <workflow>1</workflow>
|
||||
@ -71,7 +71,7 @@ class DmsfFileApiTest < RedmineDmsf::Test::IntegrationTest
|
||||
# <name>test.txt</name>
|
||||
# <content_url>http://www.example.com/dmsf/files/1/view?download=1</content_url>
|
||||
# <size>4</size>
|
||||
# <mime_type>text/plain</mime_type>
|
||||
# <content_type>text/plain</content_type>
|
||||
# <title>Test File</title>
|
||||
# <description>Some file :-)</description>
|
||||
# <workflow>1</workflow>
|
||||
@ -160,7 +160,7 @@ class DmsfFileApiTest < RedmineDmsf::Test::IntegrationTest
|
||||
assert_response :success
|
||||
revision = DmsfFileRevision.order(:created_at).last
|
||||
assert revision.present?
|
||||
assert_equal 'text/plain', revision.mime_type
|
||||
assert_equal 'text/plain', revision.content_type
|
||||
end
|
||||
|
||||
def test_upload_document_exceeded_attachment_max_size
|
||||
|
||||
@ -154,11 +154,11 @@ class DmsfWebdavGetTest < RedmineDmsf::Test::IntegrationTest
|
||||
folder = DmsfFolder.find_by(id: 1)
|
||||
assert_not_nil folder
|
||||
assert response.body.match(@folder1.title),
|
||||
"Expected to find #{folder.title} in return data"
|
||||
"Expected to find #{folder.title} in the response"
|
||||
file = DmsfFile.find_by(id: 1)
|
||||
assert_not_nil file
|
||||
assert response.body.match(file.name),
|
||||
"Expected to find #{file.name} in return data"
|
||||
"Expected to find #{file.name} in the response"
|
||||
end
|
||||
|
||||
def test_user_assigned_to_project_dmsf_module_not_enabled
|
||||
|
||||
@ -308,7 +308,7 @@ class DmsfWebdavMoveTest < RedmineDmsf::Test::IntegrationTest
|
||||
temp_file = DmsfFile.find_file_by_name @project1, nil, temp_file_name
|
||||
assert_not temp_file, "File '#{temp_file_name}' should not exist yet."
|
||||
|
||||
# Move the original file to AAAAAAAA.tmp. The original file should not changed but a new file should be created.
|
||||
# Move the original file to AAAAAAAA.tmp. The original file should not be changed but a new file should be created.
|
||||
assert_no_difference '@file1.dmsf_file_revisions.count' do
|
||||
process :move,
|
||||
"/dmsf/webdav/#{@project1.identifier}/#{@file1.name}",
|
||||
|
||||
@ -255,7 +255,7 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest
|
||||
|
||||
put "/dmsf/webdav/#{@project1.identifier}/file1.tmp", params: '1234', headers: credentials
|
||||
assert_response :success
|
||||
file1 = DmsfFile.find_by(project_id: @project1.id, dmsf_folder: nil, name: 'file1.tmp')
|
||||
file1 = DmsfFile.find_file_by_name(@project1, nil, 'file1.tmp')
|
||||
assert file1
|
||||
assert_difference 'file1.dmsf_file_revisions.count', 0 do
|
||||
put "/dmsf/webdav/#{@project1.identifier}/file1.tmp", params: '5678', headers: credentials
|
||||
@ -268,7 +268,7 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest
|
||||
|
||||
put "/dmsf/webdav/#{@project1.identifier}/~$file2.txt", params: '1234', headers: credentials
|
||||
assert_response :success
|
||||
file2 = DmsfFile.find_by(project_id: @project1.id, dmsf_folder_id: nil, name: '~$file2.txt')
|
||||
file2 = DmsfFile.find_file_by_name(@project1, nil, '~$file2.txt')
|
||||
assert file2
|
||||
assert_difference 'file2.dmsf_file_revisions.count', 0 do
|
||||
put "/dmsf/webdav/#{@project1.identifier}/~$file2.txt", params: '5678', headers: credentials
|
||||
@ -286,7 +286,7 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest
|
||||
'dmsf_webdav_strategy' => 'WEBDAV_READ_WRITE' } do
|
||||
put "/dmsf/webdav/#{@project1.identifier}/file3.dump", params: '1234', headers: credentials
|
||||
assert_response :success
|
||||
file3 = DmsfFile.find_by(project_id: @project1.id, dmsf_folder_id: nil, name: 'file3.dump')
|
||||
file3 = DmsfFile.find_file_by_name(@project1, nil, 'file3.dump')
|
||||
assert file3
|
||||
assert_difference 'file3.dmsf_file_revisions.count', 0 do
|
||||
put "/dmsf/webdav/#{@project1.identifier}/file3.dump", params: '5678', headers: credentials
|
||||
@ -304,7 +304,7 @@ class DmsfWebdavPutTest < RedmineDmsf::Test::IntegrationTest
|
||||
params: '1234',
|
||||
headers: @admin.merge!({ content_type: :text })
|
||||
assert_response :created
|
||||
assert DmsfFile.find_by(project_id: @project5.id, dmsf_folder: nil, name: 'test-1234.txt')
|
||||
assert DmsfFile.find_file_by_name(@project5, nil, 'test-1234.txt')
|
||||
end
|
||||
|
||||
def test_put_keep_title
|
||||
|
||||
@ -28,34 +28,112 @@ class DmsfFileRevisionTest < RedmineDmsf::Test::UnitTest
|
||||
@revision1 = DmsfFileRevision.find 1
|
||||
@revision2 = DmsfFileRevision.find 2
|
||||
@revision3 = DmsfFileRevision.find 3
|
||||
@revision7 = DmsfFileRevision.find 7
|
||||
@revision8 = DmsfFileRevision.find 8
|
||||
@revision13 = DmsfFileRevision.find 13
|
||||
@wf1 = DmsfWorkflow.find 1
|
||||
end
|
||||
|
||||
def test_file_title_length_validation
|
||||
file = DmsfFileRevision.new(title: Array.new(256).map { 'a' }.join,
|
||||
name: 'Test Revision',
|
||||
major_version: 1)
|
||||
assert file.invalid?
|
||||
assert_equal ['Title is too long (maximum is 255 characters)'], file.errors.full_messages
|
||||
def test_name_presence
|
||||
@revision1.name = ''
|
||||
assert @revision1.invalid?
|
||||
assert_includes @revision1.errors.full_messages, 'Name cannot be blank'
|
||||
end
|
||||
|
||||
def test_file_name_length_validation
|
||||
file = DmsfFileRevision.new(name: Array.new(256).map { 'a' }.join,
|
||||
title: 'Test Revision',
|
||||
major_version: 1)
|
||||
assert file.invalid?
|
||||
assert_equal ['Name is too long (maximum is 255 characters)'], file.errors.full_messages
|
||||
def test_title_presence
|
||||
@revision1.title = ''
|
||||
assert @revision1.invalid?
|
||||
assert_includes @revision1.errors.full_messages, 'Title cannot be blank'
|
||||
end
|
||||
|
||||
def test_file_disk_filename_length_validation
|
||||
file = DmsfFileRevision.new(disk_filename: Array.new(256).map { 'a' }.join,
|
||||
title: 'Test Revision',
|
||||
name: 'Test Revision',
|
||||
major_version: 1)
|
||||
assert file.invalid?
|
||||
assert_equal ['Disk filename is too long (maximum is 255 characters)'], file.errors.full_messages
|
||||
def test_title_length_validation
|
||||
@revision1.title = String.new('a' * 256)
|
||||
assert @revision1.invalid?
|
||||
assert_includes @revision1.errors.full_messages, 'Title is too long (maximum is 255 characters)'
|
||||
end
|
||||
|
||||
def test_name_length_validation
|
||||
@revision1.name = String.new('a' * 256)
|
||||
assert @revision1.invalid?
|
||||
assert_includes @revision1.errors.full_messages, 'Name is too long (maximum is 255 characters)'
|
||||
end
|
||||
|
||||
def test_name_uniqueness_validation
|
||||
User.current = @admin
|
||||
|
||||
# Duplicity among files names
|
||||
@revision7.name = @revision1.name
|
||||
assert @revision7.invalid?
|
||||
assert_includes @revision7.errors.full_messages, 'Name has already been taken'
|
||||
|
||||
# Duplicity among invisible files is all right
|
||||
@revision7.name = @revision3.name
|
||||
assert_not @revision7.invalid?
|
||||
|
||||
# Duplicity among files titles
|
||||
@revision7.name = @revision1.title
|
||||
assert @revision7.invalid?
|
||||
assert_includes @revision7.errors.full_messages, 'Name has already been taken'
|
||||
|
||||
# Duplicity among folders
|
||||
@revision7.name = @folder1.title
|
||||
assert @revision7.invalid?
|
||||
assert_includes @revision7.errors.full_messages, 'Name has already been taken'
|
||||
|
||||
# Duplicity among links
|
||||
@revision7.name = @folder_link1.name
|
||||
assert @revision7.invalid?
|
||||
assert_includes @revision7.errors.full_messages, 'Name has already been taken'
|
||||
|
||||
# Name is all right
|
||||
@revision7.name = 'xxx'
|
||||
assert @revision7.valid?
|
||||
end
|
||||
|
||||
def test_title_uniqueness_validation
|
||||
User.current = @admin
|
||||
|
||||
# Duplicity among files names
|
||||
@revision7.title = @revision1.name
|
||||
assert @revision7.invalid?
|
||||
assert_includes @revision7.errors.full_messages, 'Title has already been taken'
|
||||
|
||||
# Duplicity among invisible files is all right
|
||||
@revision7.title = @revision3.name
|
||||
assert_not @revision7.invalid?
|
||||
|
||||
# Duplicity among files titles
|
||||
@revision7.title = @revision1.title
|
||||
assert @revision7.invalid?
|
||||
assert_includes @revision7.errors.full_messages, 'Title has already been taken'
|
||||
|
||||
# Duplicity among folders
|
||||
@revision7.title = @folder1.title
|
||||
assert @revision7.invalid?
|
||||
assert_includes @revision7.errors.full_messages, 'Title has already been taken'
|
||||
|
||||
# Duplicity among links
|
||||
@revision7.title = @folder_link1.name
|
||||
assert @revision7.invalid?
|
||||
assert_includes @revision7.errors.full_messages, 'Title has already been taken'
|
||||
|
||||
# Name is all right
|
||||
@revision7.title = 'xxx'
|
||||
assert @revision7.valid?
|
||||
end
|
||||
|
||||
def test_name_invalid_characters_validation
|
||||
@revision1.name << DmsfFolder::INVALID_CHARACTERS[0]
|
||||
assert @revision1.invalid?
|
||||
assert_includes @revision1.errors.full_messages,
|
||||
"Name #{l('activerecord.errors.messages.error_contains_invalid_character')}"
|
||||
end
|
||||
|
||||
def test_title_invalid_characters_validation
|
||||
@revision1.title << DmsfFolder::INVALID_CHARACTERS[0]
|
||||
assert @revision1.invalid?
|
||||
assert_includes @revision1.errors.full_messages,
|
||||
"Title #{l('activerecord.errors.messages.error_contains_invalid_character')}"
|
||||
end
|
||||
|
||||
def test_delete_restore
|
||||
@ -70,56 +148,6 @@ class DmsfFileRevisionTest < RedmineDmsf::Test::UnitTest
|
||||
assert_nil DmsfFileRevision.find_by(id: @revision13.id)
|
||||
end
|
||||
|
||||
def test_new_storage_filename
|
||||
# Create a file.
|
||||
f = DmsfFile.new
|
||||
f.project_id = 1
|
||||
f.name = 'Testfile.txt'
|
||||
f.dmsf_folder = nil
|
||||
f.notification = RedmineDmsf.dmsf_default_notifications?
|
||||
f.save
|
||||
|
||||
# Create two new revisions, r1 and r2
|
||||
r1 = DmsfFileRevision.new
|
||||
r1.minor_version = 0
|
||||
r1.major_version = 1
|
||||
r1.dmsf_file = f
|
||||
r1.user = User.current
|
||||
r1.name = 'Testfile.txt'
|
||||
r1.title = DmsfFileRevision.filename_to_title(r1.name)
|
||||
r1.description = nil
|
||||
r1.comment = nil
|
||||
r1.size = 4
|
||||
|
||||
r2 = r1.clone
|
||||
r2.minor_version = 1
|
||||
|
||||
assert r1.valid?
|
||||
assert r2.valid?
|
||||
|
||||
# This is a very stupid since the generation and storing of files below must be done during the
|
||||
# same second, so wait until the microsecond part of the DateTime is less than 10 ms, should be
|
||||
# plenty of time to do the rest then.
|
||||
wait_timeout = 2_000
|
||||
while DateTime.current.usec > 10_000
|
||||
wait_timeout -= 10
|
||||
flunk 'Waited too long.' if wait_timeout <= 0
|
||||
sleep 0.01
|
||||
end
|
||||
|
||||
# First, generate the r1 storage filename and save the file
|
||||
r1.disk_filename = r1.new_storage_filename
|
||||
assert r1.save
|
||||
# Just make sure the file exists
|
||||
File.binwrite r1.disk_file, '1234'
|
||||
|
||||
# Directly after the file has been stored generate the r2 storage filename.
|
||||
# Hopefully the seconds part of the DateTime.current has not changed and the generated filename will
|
||||
# be on the same second but it should then be increased by 1.
|
||||
r2.disk_filename = r2.new_storage_filename
|
||||
assert_not_equal r1.disk_filename, r2.disk_filename, 'The disk filename should not be equal for two revisions.'
|
||||
end
|
||||
|
||||
def test_invalid_filename_extension
|
||||
with_settings(attachment_extensions_allowed: 'txt') do
|
||||
r1 = DmsfFileRevision.new
|
||||
@ -334,4 +362,12 @@ class DmsfFileRevisionTest < RedmineDmsf::Test::UnitTest
|
||||
def test_checksum
|
||||
assert_equal @revision1.checksum, @revision1.file.blob.checksum
|
||||
end
|
||||
|
||||
def test_content_type
|
||||
assert_equal 'text/plain', @revision1.content_type
|
||||
@revision1.file.blob.content_type = ''
|
||||
assert_equal 'text/plain', @revision1.content_type
|
||||
@revision1.file.blob.filename = 'data'
|
||||
assert_equal 'application/octet-stream', @revision1.content_type
|
||||
end
|
||||
end
|
||||
|
||||
@ -28,12 +28,6 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest
|
||||
@wf2 = DmsfWorkflow.find 2
|
||||
end
|
||||
|
||||
def test_file_name_length_validation
|
||||
file = DmsfFile.new(name: Array.new(256).map { 'a' }.join)
|
||||
assert file.invalid?
|
||||
assert_equal ['Name is too long (maximum is 255 characters)'], file.errors.full_messages
|
||||
end
|
||||
|
||||
def test_project_file_count_differs_from_project_visibility_count
|
||||
assert_not_same @project1.dmsf_files.all.size, @project1.dmsf_files.visible.all.size
|
||||
end
|
||||
@ -188,15 +182,13 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest
|
||||
# Text
|
||||
assert_equal 'attachment', @file1.disposition
|
||||
# Image
|
||||
assert_equal 'inline', @file7.disposition
|
||||
assert_equal 'inline', @file6.disposition
|
||||
# PDF
|
||||
assert_equal 'inline', @file8.disposition
|
||||
assert_equal 'inline', @file7.disposition
|
||||
# Video
|
||||
@file1.last_revision.disk_filename = 'test.mp4'
|
||||
assert_equal 'inline', @file1.disposition
|
||||
assert_equal 'inline', @file6.disposition
|
||||
# HTML
|
||||
@file1.last_revision.disk_filename = 'test.html'
|
||||
assert_equal 'inline', @file1.disposition
|
||||
assert_equal 'inline', @file14.disposition
|
||||
end
|
||||
|
||||
def test_image
|
||||
@ -209,8 +201,6 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest
|
||||
assert @file1.text?
|
||||
assert_not @file7.text?
|
||||
assert_not @file8.text?
|
||||
@file1.last_revision.disk_filename = 'test.c'
|
||||
assert @file1.text?
|
||||
end
|
||||
|
||||
def test_pdf
|
||||
@ -221,25 +211,28 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest
|
||||
|
||||
def test_video
|
||||
assert_not @file1.video?
|
||||
@file1.last_revision.disk_filename = 'test.mp4'
|
||||
assert @file1.video?
|
||||
assert @file6.video?
|
||||
end
|
||||
|
||||
def test_html
|
||||
assert_not @file1.html?
|
||||
@file1.last_revision.disk_filename = 'test.html'
|
||||
assert @file1.html?
|
||||
assert @file14.html?
|
||||
end
|
||||
|
||||
def test_office_doc
|
||||
assert_not @file1.office_doc?
|
||||
assert @file13.office_doc?
|
||||
end
|
||||
|
||||
def test_markdown
|
||||
assert_not @file1.markdown?
|
||||
@file1.last_revision.disk_filename = 'test.md'
|
||||
@file1.last_revision.file.blob.content_type = 'text/markdown'
|
||||
assert @file1.markdown?
|
||||
end
|
||||
|
||||
def test_textile
|
||||
assert_not @file1.textile?
|
||||
@file1.last_revision.disk_filename = 'test.textile'
|
||||
@file1.last_revision.file.blob.content_type = 'text/textile'
|
||||
assert @file1.textile?
|
||||
end
|
||||
|
||||
@ -293,10 +286,6 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest
|
||||
assert @file1.watched_by?(@jsmith)
|
||||
end
|
||||
|
||||
def test_office_doc
|
||||
assert @file13.office_doc?
|
||||
end
|
||||
|
||||
def test_previewable
|
||||
assert(@file13.previewable?) if RedmineDmsf::Preview.office_available?
|
||||
end
|
||||
|
||||
@ -21,9 +21,64 @@ require File.expand_path('../../test_helper', __FILE__)
|
||||
|
||||
# Folder tests
|
||||
class DmsfFolderTest < RedmineDmsf::Test::UnitTest
|
||||
include Redmine::I18n
|
||||
|
||||
def setup
|
||||
super
|
||||
@link2 = DmsfLink.find 2
|
||||
@revision1 = DmsfFileRevision.find 1
|
||||
@revision3 = DmsfFileRevision.find 3
|
||||
end
|
||||
|
||||
def test_title_presence
|
||||
@folder1.title = ''
|
||||
assert @folder1.invalid?
|
||||
assert_includes @folder1.errors.full_messages, 'Title cannot be blank'
|
||||
end
|
||||
|
||||
def test_title_length_validation
|
||||
@folder1.title = String.new('a' * 256)
|
||||
assert @folder1.invalid?
|
||||
assert_includes @folder1.errors.full_messages, 'Title is too long (maximum is 255 characters)'
|
||||
end
|
||||
|
||||
def test_title_invalid_characters_validation
|
||||
@folder1.title << DmsfFolder::INVALID_CHARACTERS[0]
|
||||
assert @folder1.invalid?
|
||||
assert_includes @folder1.errors.full_messages,
|
||||
"Title #{l('activerecord.errors.messages.error_contains_invalid_character')}"
|
||||
end
|
||||
|
||||
def test_title_uniqueness_validation
|
||||
User.current = @admin
|
||||
|
||||
# Duplicity among files names
|
||||
@folder1.title = @revision1.name
|
||||
assert @folder1.invalid?
|
||||
assert_includes @folder1.errors.full_messages, 'Title has already been taken'
|
||||
|
||||
# Duplicity among invisible files is all right
|
||||
@folder1.title = @revision3.name
|
||||
assert_not @folder1.invalid?
|
||||
|
||||
# Duplicity among files titles
|
||||
@folder1.title = @revision1.title
|
||||
assert @folder1.invalid?
|
||||
assert_includes @folder1.errors.full_messages, 'Title has already been taken'
|
||||
|
||||
# Duplicity among folders
|
||||
@folder1.title = @folder6.title
|
||||
assert @folder1.invalid?
|
||||
assert_includes @folder1.errors.full_messages, 'Title has already been taken'
|
||||
|
||||
# Duplicity among links
|
||||
@folder1.title = @folder_link1.name
|
||||
assert @folder1.invalid?
|
||||
assert_includes @folder1.errors.full_messages, 'Title has already been taken'
|
||||
|
||||
# Name is all right
|
||||
@folder1.title = 'xxx'
|
||||
assert @folder1.valid?
|
||||
end
|
||||
|
||||
def test_visibility
|
||||
|
||||
@ -21,6 +21,14 @@ require File.expand_path('../../test_helper', __FILE__)
|
||||
|
||||
# Link tests
|
||||
class DmsfLinksTest < RedmineDmsf::Test::UnitTest
|
||||
include Redmine::I18n
|
||||
|
||||
def setup
|
||||
super
|
||||
@revision1 = DmsfFileRevision.find 1
|
||||
@revision3 = DmsfFileRevision.find 3
|
||||
end
|
||||
|
||||
def test_create_folder_link
|
||||
folder_link = DmsfLink.new
|
||||
folder_link.target_project_id = @project1.id
|
||||
@ -57,43 +65,77 @@ class DmsfLinksTest < RedmineDmsf::Test::UnitTest
|
||||
assert external_link.save, external_link.errors.full_messages.to_sentence
|
||||
end
|
||||
|
||||
def test_validate_name_length
|
||||
@folder_link1.name = ('a' * 256)
|
||||
assert_not @folder_link1.save, "Folder link #{@folder_link1.name} should have not been saved"
|
||||
assert_equal 1, @folder_link1.errors.size
|
||||
def test_name_length_validation
|
||||
@folder_link1.name = String.new('a' * 256)
|
||||
assert @folder_link1.invalid?
|
||||
assert_includes @folder_link1.errors.full_messages, 'Name is too long (maximum is 255 characters)'
|
||||
end
|
||||
|
||||
def test_validate_name_presence
|
||||
@folder_link1.name = ''
|
||||
assert_not @folder_link1.save, "Folder link #{@folder_link1.name} should have not been saved"
|
||||
assert_equal 1, @folder_link1.errors.size
|
||||
assert @folder_link1.invalid?
|
||||
assert_includes @folder_link1.errors.full_messages, 'Name cannot be blank'
|
||||
end
|
||||
|
||||
def test_title_invalid_characters_validation
|
||||
@folder_link1.name << DmsfFolder::INVALID_CHARACTERS[0]
|
||||
assert @folder_link1.invalid?
|
||||
assert_includes @folder_link1.errors.full_messages,
|
||||
"Name #{l('activerecord.errors.messages.error_contains_invalid_character')}"
|
||||
end
|
||||
|
||||
def test_name_uniqueness_validation
|
||||
User.current = @admin
|
||||
|
||||
# Duplicity among files names
|
||||
@folder_link1.name = @revision1.name
|
||||
assert @folder_link1.invalid?
|
||||
assert_includes @folder_link1.errors.full_messages, 'Name has already been taken'
|
||||
|
||||
# Duplicity among invisible files is all right
|
||||
@folder_link1.name = @revision3.name
|
||||
assert_not @folder_link1.invalid?
|
||||
|
||||
# Duplicity among files titles
|
||||
@folder_link1.name = @revision1.title
|
||||
assert @folder_link1.invalid?
|
||||
assert_includes @folder_link1.errors.full_messages, 'Name has already been taken'
|
||||
|
||||
# Duplicity among folders
|
||||
@folder_link1.name = @folder6.title
|
||||
assert @folder_link1.invalid?
|
||||
assert_includes @folder_link1.errors.full_messages, 'Name has already been taken'
|
||||
|
||||
# Name is all right
|
||||
@folder_link1.name = 'xxx'
|
||||
assert @folder_link1.valid?
|
||||
end
|
||||
|
||||
def test_validate_external_url_length
|
||||
@file_link2.target_type = 'DmsfUrl'
|
||||
@file_link2.external_url = "https://localhost/#{'a' * 256}"
|
||||
assert_not @file_link2.save, "External URL link #{@file_link2.name} should have not been saved"
|
||||
assert_equal 1, @file_link2.errors.size
|
||||
assert @file_link2.invalid?
|
||||
assert_includes @file_link2.errors.full_messages, 'URL is too long (maximum is 255 characters)'
|
||||
end
|
||||
|
||||
def test_validate_external_url_presence
|
||||
@file_link2.target_type = 'DmsfUrl'
|
||||
@file_link2.external_url = ''
|
||||
assert_not @file_link2.save, "External URL link #{@file_link2.name} should have not been saved"
|
||||
assert_equal 1, @file_link2.errors.size
|
||||
assert @file_link2.invalid?
|
||||
assert_includes @file_link2.errors.full_messages, 'URL is invalid'
|
||||
end
|
||||
|
||||
def test_validate_external_url_invalid
|
||||
@file_link2.target_type = 'DmsfUrl'
|
||||
@file_link2.external_url = 'htt ps://abc.xyz'
|
||||
assert_not @file_link2.save, "External URL link #{@file_link2.name} should have not been saved"
|
||||
assert_equal 1, @file_link2.errors.size
|
||||
assert @file_link2.invalid?
|
||||
assert_includes @file_link2.errors.full_messages, 'URL is invalid'
|
||||
end
|
||||
|
||||
def test_validate_external_url_valid
|
||||
@file_link2.target_type = 'DmsfUrl'
|
||||
@file_link2.external_url = 'https://www.google.com/search?q=寿司'
|
||||
assert @file_link2.save
|
||||
assert @file_link2.valid?
|
||||
end
|
||||
|
||||
def test_belongs_to_project
|
||||
@ -186,7 +228,7 @@ class DmsfLinksTest < RedmineDmsf::Test::UnitTest
|
||||
def test_copy_to_author
|
||||
assert_equal @admin.id, @file_link2.user_id
|
||||
User.current = @jsmith
|
||||
l = @file_link2.copy_to(@project1, @folder1)
|
||||
l = @file_link2.copy_to(@project1, @folder6)
|
||||
assert l
|
||||
assert_equal @jsmith.id, l.user_id, 'Author must be updated when copying'
|
||||
end
|
||||
|
||||
@ -37,10 +37,14 @@ class DmsfQueryTest < RedmineDmsf::Test::UnitTest
|
||||
end
|
||||
|
||||
def test_dmsf_count
|
||||
n = DmsfFolder.visible.where(project_id: @project1.id).where("title LIKE '%test%'").all.size +
|
||||
DmsfFile.visible.where(project_id: @project1.id).where("name LIKE '%test%'").all.size +
|
||||
DmsfLink.visible.where(project_id: @project1.id).where("name LIKE '%test%'").all.size
|
||||
assert_equal n - 1, @query401.dmsf_count # One folder is not visible due to the permissions
|
||||
n = DmsfFolder.where(project_id: @project1.id).where("title LIKE '%test%'").all.size +
|
||||
DmsfLink.where(project_id: @project1.id).where("name LIKE '%test%'").all.size
|
||||
|
||||
DmsfFile.where(project_id: @project1.id).find_each do |file|
|
||||
n += 1 if file.name.include?('test')
|
||||
end
|
||||
|
||||
assert_equal n - 2, @query401.dmsf_count # Two file are not visible due to folder's permissions
|
||||
end
|
||||
|
||||
def test_dmsf_nodes
|
||||
|
||||
@ -350,7 +350,7 @@ class DmsfMacrosTest < RedmineDmsf::Test::HelperTest
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
title: h(@file7.last_revision.try(:tooltip)),
|
||||
'data-downloadurl' => "#{@file7.last_revision.detect_content_type}:#{h(@file7.name)}:#{url}")
|
||||
'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
||||
assert text.include?(link), text
|
||||
end
|
||||
|
||||
@ -379,7 +379,7 @@ class DmsfMacrosTest < RedmineDmsf::Test::HelperTest
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
title: h(@file7.last_revision.try(:tooltip)),
|
||||
'data-downloadurl' => "#{@file7.last_revision.detect_content_type}:#{h(@file7.name)}:#{url}")
|
||||
'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
||||
assert text.include?(link), text
|
||||
# TODO: arguments src and with and height are swapped
|
||||
# size = '640x480'
|
||||
@ -390,7 +390,7 @@ class DmsfMacrosTest < RedmineDmsf::Test::HelperTest
|
||||
# target: '_blank',
|
||||
# rel: 'noopener',
|
||||
# title: h(@file7.last_revision.try(:tooltip)),
|
||||
# 'data-downloadurl' => "#{@file7.last_revision.detect_content_type}:#{h(@file7.name)}:#{url}")
|
||||
# 'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
||||
# assert text.include?(link), text
|
||||
height = '480'
|
||||
text = textilizable("{{dmsftn(#{@file7.id}, height=#{height})}}")
|
||||
@ -410,7 +410,7 @@ class DmsfMacrosTest < RedmineDmsf::Test::HelperTest
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
title: h(@file7.last_revision.try(:tooltip)),
|
||||
'data-downloadurl' => "#{@file7.last_revision.detect_content_type}:#{h(@file7.name)}:#{url}")
|
||||
'data-downloadurl' => "#{@file7.last_revision.content_type}:#{h(@file7.name)}:#{url}")
|
||||
assert text.include?(link), text
|
||||
end
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ class ProjectPatchTest < RedmineDmsf::Test::UnitTest
|
||||
def test_dmsf_count
|
||||
User.current = @jsmith
|
||||
hash = @project1.dmsf_count
|
||||
assert_equal 9, hash[:files]
|
||||
assert_equal 10, hash[:files]
|
||||
assert_equal 5, hash[:folders]
|
||||
end
|
||||
|
||||
@ -70,7 +70,7 @@ class ProjectPatchTest < RedmineDmsf::Test::UnitTest
|
||||
def test_copy_dmsf
|
||||
User.current = @jsmith
|
||||
|
||||
assert_equal 5, @project1.dmsf_files.visible.all.size
|
||||
assert_equal 6, @project1.dmsf_files.visible.all.size
|
||||
assert_equal 3, @project1.dmsf_folders.visible.all.size
|
||||
assert_equal 2, @project1.file_links.all.size
|
||||
assert_equal 1, @project1.folder_links.all.size
|
||||
@ -84,7 +84,7 @@ class ProjectPatchTest < RedmineDmsf::Test::UnitTest
|
||||
|
||||
@project5.copy_dmsf @project1
|
||||
|
||||
assert_equal 6, @project5.dmsf_files.all.size
|
||||
assert_equal 7, @project5.dmsf_files.all.size
|
||||
assert_equal 4, @project5.dmsf_folders.all.size
|
||||
assert_equal 2, @project5.file_links.all.size
|
||||
assert_equal 1, @project5.folder_links.all.size
|
||||
|
||||
@ -56,10 +56,12 @@ module RedmineDmsf
|
||||
@file2 = DmsfFile.find 2
|
||||
@file4 = DmsfFile.find 4
|
||||
@file5 = DmsfFile.find 5
|
||||
@file6 = DmsfFile.find 6
|
||||
@file7 = DmsfFile.find 7
|
||||
@file8 = DmsfFile.find 8
|
||||
@file11 = DmsfFile.find 11
|
||||
@file13 = DmsfFile.find 13
|
||||
@file14 = DmsfFile.find 14
|
||||
@folder_link1 = DmsfLink.find 1
|
||||
@file_link2 = DmsfLink.find 2
|
||||
@file_link7 = DmsfLink.find 7
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user