#716 dav4rack -> planio/dav4rack
This commit is contained in:
parent
28f9ab48ff
commit
3f1e6148ca
@ -3,9 +3,10 @@ Changelog for Redmine DMSF
|
||||
|
||||
1.6.1 *2018-??-??*
|
||||
------------------
|
||||
|
||||
WebDAV caching optionable in the plugin settings. Default is off.
|
||||
|
||||
Javascript on pages is loaded asynchronously.
|
||||
Obsolete Dav4Rack gem replaced with an up to date fork by Planio (Consequently WebDAV caching has been removed, sorry...).
|
||||
Project members can be chosen as recipients when sending documents by email.
|
||||
|
||||
1.6.0 *2017-09-12*
|
||||
------------------
|
||||
|
||||
3
Gemfile
3
Gemfile
@ -26,7 +26,8 @@ gem 'rubyzip', '>= 1.0.0'
|
||||
gem 'zip-zip'
|
||||
gem 'simple_enum'
|
||||
gem 'uuidtools'
|
||||
gem 'dav4rack'
|
||||
#gem 'dav4rack'
|
||||
gem 'dav4rack', git: 'https://github.com/planio-gmbh/dav4rack.git', branch: 'master'
|
||||
gem 'dalli'
|
||||
unless %w(easyproject easy_gantt).any? { |plugin| Dir.exist?(File.expand_path("../../#{plugin}", __FILE__)) }
|
||||
gem 'redmine_extensions', '~> 0.2.5'
|
||||
|
||||
23
README.md
23
README.md
@ -260,14 +260,8 @@ Before installing ensure that the Redmine instance is stopped.
|
||||
Example:
|
||||
|
||||
rake redmine:dmsf_alert_approvals RAILS_ENV="production"
|
||||
|
||||
III) To clears the Webdav Cache
|
||||
|
||||
Example:
|
||||
|
||||
rake redmine:dmsf_clear_webdav_cache RAILS_ENV="production"
|
||||
|
||||
IV) To create missing MD5 digest for all file revisions
|
||||
|
||||
III) To create missing MD5 digest for all file revisions
|
||||
|
||||
Available options:
|
||||
|
||||
@ -278,7 +272,7 @@ Before installing ensure that the Redmine instance is stopped.
|
||||
bundle exec rake redmine:dmsf_create_digests RAILS_ENV="production"
|
||||
bundle exec rake redmine:dmsf_create_digests dry_run=1 RAILS_ENV="production"
|
||||
|
||||
V) To maintain DMSF
|
||||
IV) To maintain DMSF
|
||||
|
||||
* Remove all files with no database record from the document directory
|
||||
* Remove all links project_id = -1 (added links to an issue which hasn't been created)
|
||||
@ -309,17 +303,6 @@ Example of cron job (once per hour at 8th minute):
|
||||
|
||||
See redmine_dmsf/extra/xapian_indexer.rb for help.
|
||||
|
||||
### WebDAV caching (optional)
|
||||
Creating the file lists for the WebDAV takes a lot of resources, for folders with many files it can take several seconds
|
||||
and for clients that don't cache the lists (Windows WebClient!) a new list must be created every time you browse into that folder, even if nothing has changed in the folder so browsing a WebDAV share in Windows is not a pleasant experience.
|
||||
By enabling caching the response time can be significantly reduced from several seconds for folders with hundreds of items down to a few milliseconds.
|
||||
|
||||
To enable caching you must have a memcached server installed.
|
||||
Follow the installation instructions at <https://github.com/memcached/memcached/wiki>, write the address/ip to the memcached server in the DMSF plugin configuration and then restart Redmine.
|
||||
If you installed your memcached server on the same machine as your Redmine installation then you can use 'localhost' as the memcached server address.
|
||||
Only one server is supported, and it has only been tested using 'localhost'.
|
||||
To disable caching just clear the memcached server address and restart Redmine.
|
||||
|
||||
Uninstalling DMSF
|
||||
-----------------
|
||||
Before uninstalling the DMSF plugin, please ensure that the Redmine instance is stopped.
|
||||
|
||||
@ -88,3 +88,6 @@ ActionDispatch::Reloader.to_prepare do
|
||||
Redmine::Activity.register :dmsf_file_revision_accesses, :default => false
|
||||
Redmine::Activity.register :dmsf_file_revisions
|
||||
end
|
||||
|
||||
# WebDAV
|
||||
Rails.configuration.middleware.insert_before ActionDispatch::Cookies, RedmineDmsf::Webdav::CustomMiddleware
|
||||
|
||||
@ -239,8 +239,6 @@ class DmsfFile < ActiveRecord::Base
|
||||
errors[:base] << l(:error_file_is_locked)
|
||||
return false
|
||||
end
|
||||
# Must invalidate source parent folder cache before moving
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
source = "#{self.project.identifier}:#{self.dmsf_path_str}"
|
||||
self.project_id = project.id
|
||||
self.dmsf_folder = folder
|
||||
@ -478,35 +476,6 @@ class DmsfFile < ActiveRecord::Base
|
||||
nil
|
||||
end
|
||||
|
||||
def save(*args)
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super(*args)
|
||||
end
|
||||
|
||||
def save!(*args)
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super(*args)
|
||||
end
|
||||
|
||||
def destroy
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super
|
||||
end
|
||||
|
||||
def destroy!
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super
|
||||
end
|
||||
|
||||
def propfind_cache_key
|
||||
unless dmsf_folder_id
|
||||
# File is in project root
|
||||
return "PROPFIND/#{self.project_id}"
|
||||
else
|
||||
return "PROPFIND/#{self.project_id}/#{self.dmsf_folder_id}"
|
||||
end
|
||||
end
|
||||
|
||||
def extension
|
||||
File.extname(self.last_revision.disk_filename).strip.downcase[1..-1] if self.last_revision
|
||||
end
|
||||
|
||||
@ -111,7 +111,6 @@ class DmsfFileRevision < ActiveRecord::Base
|
||||
dependencies = DmsfFileRevision.where(:disk_filename => self.disk_filename).count
|
||||
File.delete(self.disk_file) if dependencies <= 1 && File.exist?(self.disk_file)
|
||||
end
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super
|
||||
end
|
||||
|
||||
@ -318,20 +317,6 @@ class DmsfFileRevision < ActiveRecord::Base
|
||||
end
|
||||
ActionView::Base.full_sanitizer.sanitize(text)
|
||||
end
|
||||
|
||||
def save(*args)
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super(*args)
|
||||
end
|
||||
|
||||
def save!(*args)
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super(*args)
|
||||
end
|
||||
|
||||
def propfind_cache_key
|
||||
dmsf_file.propfind_cache_key
|
||||
end
|
||||
|
||||
def workflow_tooltip
|
||||
tooltip = ''
|
||||
|
||||
@ -349,6 +349,7 @@ class DmsfFolder < ActiveRecord::Base
|
||||
|
||||
# Number of items in the folder
|
||||
def items
|
||||
Rails.logger.info ">>> #{dmsf_files.visible.where(:project_id => self.project_id).to_sql}"
|
||||
dmsf_folders.visible.where(:project_id => self.project_id).count +
|
||||
dmsf_files.visible.where(:project_id => self.project_id).count +
|
||||
dmsf_links.visible.where(:project_id => self.project_id).count
|
||||
@ -453,35 +454,6 @@ class DmsfFolder < ActiveRecord::Base
|
||||
nil
|
||||
end
|
||||
|
||||
def save(*args)
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super(*args)
|
||||
end
|
||||
|
||||
def save!(*args)
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super(*args)
|
||||
end
|
||||
|
||||
def destroy
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super
|
||||
end
|
||||
|
||||
def destroy!
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
|
||||
super
|
||||
end
|
||||
|
||||
def propfind_cache_key
|
||||
if dmsf_folder_id.nil?
|
||||
# Folder is in project root
|
||||
return "PROPFIND/#{project_id}"
|
||||
else
|
||||
return "PROPFIND/#{project_id}/#{dmsf_folder_id}"
|
||||
end
|
||||
end
|
||||
|
||||
include ActionView::Helpers::NumberHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
|
||||
@ -356,9 +356,6 @@ de:
|
||||
label_dmsf_attachments: DMS Anhänge
|
||||
label_basic_attachments: Dateianhängen
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ en:
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ es:
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ fr:
|
||||
label_dmsf_attachments: Pièces-jointes DMS
|
||||
label_basic_attachments: Pièces-jointes standards
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ hu:
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ it: # Italian strings thx 2 Matteo Arceci!
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ ja:
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ pl:
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ pt-BR:
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ ru:
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ sl:
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ zh-TW:
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -359,9 +359,6 @@ zh:
|
||||
label_dmsf_attachments: DMS Attachments
|
||||
label_basic_attachments: Basic Attachments
|
||||
|
||||
label_caching_enabled: Caching enabled
|
||||
note_webdav_cahing_enabled: PROPFIND responses are cached in order to speed up WebDAV communication
|
||||
|
||||
label_tmpdir: Temporary file path
|
||||
error_file_tmpdir_does_not_exist: "Temporary file path doesn't exist and can't be created"
|
||||
error_tmpfile_can_not_be_created: "Files can't be created in temporary file path directory"
|
||||
|
||||
@ -118,19 +118,6 @@ if Redmine::Plugin.installed? :redmine_dmsf
|
||||
post '/dmsf/folders/:id/copy/move', :controller => 'dmsf_folders_copy', :action => 'move'
|
||||
get '/dmsf/folders/:id/copy', :controller => 'dmsf_folders_copy', :action => 'new', :as => 'copy_folder'
|
||||
|
||||
#
|
||||
# DAV4Rack implementation of Webdav
|
||||
dav_engine = DAV4Rack::Handler.new(
|
||||
:root_uri_path => "#{Redmine::Utils::relative_url_root}/dmsf/webdav",
|
||||
:resource_class => RedmineDmsf::Webdav::ResourceProxy,
|
||||
:controller_class => RedmineDmsf::Webdav::Controller,
|
||||
:log_to => Rails.logger
|
||||
)
|
||||
mount dav_engine, :at => '/dmsf/webdav'
|
||||
mount dav_engine, :at => '/', :via => :options
|
||||
mount dav_engine, :at => '/', :via => :propfind
|
||||
mount dav_engine, :at => '/dmsf', :via => :propfind
|
||||
|
||||
# Approval workflow
|
||||
resources :dmsf_workflows do
|
||||
member do
|
||||
|
||||
@ -41,11 +41,9 @@ if defined?(EasyExtensions)
|
||||
end
|
||||
|
||||
# Load up classes that make up our WebDAV solution ontop of DAV4Rack
|
||||
require 'redmine_dmsf/webdav/custom_middleware'
|
||||
require 'redmine_dmsf/webdav/base_resource'
|
||||
require 'redmine_dmsf/webdav/controller'
|
||||
require 'redmine_dmsf/webdav/cache'
|
||||
require 'redmine_dmsf/webdav/dmsf_resource'
|
||||
require 'redmine_dmsf/webdav/download'
|
||||
require 'redmine_dmsf/webdav/index_resource'
|
||||
require 'redmine_dmsf/webdav/project_resource'
|
||||
require 'redmine_dmsf/webdav/resource_proxy'
|
||||
|
||||
@ -73,12 +73,6 @@ module RedmineDmsf
|
||||
l.save!
|
||||
reload
|
||||
locks.reload
|
||||
|
||||
# Invalidate PROPFIND (for parent folder)
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(self.propfind_cache_key)
|
||||
# Invalidate PROPSTATS
|
||||
RedmineDmsf::Webdav::Cache.delete("PROPSTATS/#{RedmineDmsf::Webdav::ProjectResource.create_project_name(self.project)}/#{self.dmsf_path_str}") if self.is_a?(DmsfFolder)
|
||||
RedmineDmsf::Webdav::Cache.delete("PROPSTATS/#{self.id}-#{self.last_revision.id}") if self.is_a?(DmsfFile)
|
||||
return l
|
||||
end
|
||||
|
||||
@ -142,13 +136,6 @@ module RedmineDmsf
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Invalidate PROPFIND (for parent folder)
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(self.propfind_cache_key)
|
||||
# Invalidate PROPSTATS
|
||||
RedmineDmsf::Webdav::Cache.delete("PROPSTATS/#{RedmineDmsf::Webdav::ProjectResource.create_project_name(self.project)}/#{self.dmsf_path_str}") if self.is_a?(DmsfFolder)
|
||||
RedmineDmsf::Webdav::Cache.delete("PROPSTATS/#{self.id}-#{self.last_revision.id}") if self.is_a?(DmsfFile)
|
||||
|
||||
reload
|
||||
locks.reload
|
||||
end
|
||||
|
||||
@ -27,11 +27,13 @@ module RedmineDmsf
|
||||
include Redmine::I18n
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
def initialize(*args)
|
||||
webdav_setting = Setting.plugin_redmine_dmsf['dmsf_webdav']
|
||||
raise NotFound if webdav_setting.nil? || webdav_setting.empty?
|
||||
attr_reader :public_path
|
||||
|
||||
def initialize(path, request, response, options)
|
||||
raise NotFound unless Setting.plugin_redmine_dmsf['dmsf_webdav'].present?
|
||||
@project = nil
|
||||
super(*args)
|
||||
@public_path = "#{options[:root_uri_path]}#{path}"
|
||||
super(path, request, response, options)
|
||||
end
|
||||
|
||||
DIR_FILE = "<tr><td class=\"name\"><a href=\"%s\">%s</a></td><td class=\"size\">%s</td><td class=\"type\">%s</td><td class=\"mtime\">%s</td></tr>"
|
||||
@ -49,16 +51,6 @@ module RedmineDmsf
|
||||
def special_type
|
||||
nil
|
||||
end
|
||||
|
||||
# Base attribute overriding in order to allow non-ascii characters in path
|
||||
def path
|
||||
@path.force_encoding('utf-8')
|
||||
end
|
||||
|
||||
# Base attribute overriding in order to allow non-ascii characters in path
|
||||
def public_path
|
||||
@public_path.force_encoding('utf-8')
|
||||
end
|
||||
|
||||
# Generate HTML for Get requests, or Head requests if no_body is true
|
||||
def html_display
|
||||
@ -66,7 +58,7 @@ module RedmineDmsf
|
||||
Confict unless collection?
|
||||
entities = children.map{|child|
|
||||
DIR_FILE % [
|
||||
child.public_path,
|
||||
"#{@options[:root_uri_path]}#{child.path}",
|
||||
child.long_name || child.name,
|
||||
child.collection? ? '' : number_to_human_size(child.content_length),
|
||||
child.special_type || child.content_type,
|
||||
@ -80,34 +72,24 @@ module RedmineDmsf
|
||||
'',
|
||||
'',
|
||||
] + entities if parent
|
||||
@response.body << index_page % [ path.empty? ? '/' : path, path.empty? ? '/' : path, entities ]
|
||||
@response.body << index_page % [ @path.empty? ? '/' : @path, @path.empty? ? '/' : @path, entities ]
|
||||
end
|
||||
|
||||
# Run method through proxy class - ensuring always compatible child is generated
|
||||
def child(name)
|
||||
new_public = public_path.dup
|
||||
new_public = new_public + '/' unless new_public[-1,1] == '/'
|
||||
new_public = '/' + new_public unless new_public[0,1] == '/'
|
||||
new_path = path.dup
|
||||
new_path = @path.dup
|
||||
new_path = new_path + '/' unless new_path[-1,1] == '/'
|
||||
new_path = '/' + new_path unless new_path[0,1] == '/'
|
||||
@__proxy.class.new("#{new_public}#{name}", "#{new_path}#{name}", request, response, options.merge(:user => @user))
|
||||
@__proxy.class.new("#{new_path}#{name}", request, response, @options.merge(:user => @user))
|
||||
end
|
||||
|
||||
def child_project(p)
|
||||
project_display_name = ProjectResource.create_project_name(p)
|
||||
|
||||
new_public = public_path.dup
|
||||
new_public = new_public + '/' unless new_public[-1,1] == '/'
|
||||
new_public = '/' + new_public unless new_public[0,1] == '/'
|
||||
new_public += project_display_name
|
||||
|
||||
new_path = path.dup
|
||||
new_path = @path.dup
|
||||
new_path = new_path + '/' unless new_path[-1,1] == '/'
|
||||
new_path = '/' + new_path unless new_path[0,1] == '/'
|
||||
new_path += project_display_name
|
||||
|
||||
@__proxy.class.new("#{new_public}", "#{new_path}", request, response, options.merge(:user => @user))
|
||||
@__proxy.class.new(new_path, request, response, @options.merge(:user => @user))
|
||||
end
|
||||
|
||||
def parent
|
||||
@ -118,47 +100,85 @@ module RedmineDmsf
|
||||
|
||||
# Override index_page from DAV4Rack::Resource
|
||||
def index_page
|
||||
return <<-PAGE
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
<head>
|
||||
<title>Index of %s</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<style type='text/css'>
|
||||
table { width:100%%; }
|
||||
.name { text-align:left; }
|
||||
.size { text-align: center; }
|
||||
.type { text-align: center; width: 11em; }
|
||||
.mtime { width:15em; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Index of %s</h1>
|
||||
<hr/>
|
||||
<table>
|
||||
<tr>
|
||||
<th class='name'>Name</th>
|
||||
<th class='size'>Size</th>
|
||||
<th class='type'>Type</th>
|
||||
<th class='mtime'>Last Modified</th>
|
||||
</tr>
|
||||
%s
|
||||
</table>
|
||||
<hr/>
|
||||
</body>
|
||||
</html>
|
||||
PAGE
|
||||
return %{
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||
<head>
|
||||
<title>Index of %s</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<style type='text/css'>
|
||||
table { width:100%%; }
|
||||
.name { text-align:left; }
|
||||
.size { text-align: center; }
|
||||
.type { text-align: center; width: 11em; }
|
||||
.mtime { width:15em; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Index of %s</h1>
|
||||
<hr/>
|
||||
<table>
|
||||
<tr>
|
||||
<th class='name'>Name</th>
|
||||
<th class='size'>Size</th>
|
||||
<th class='type'>Type</th>
|
||||
<th class='mtime'>Last Modified</th>
|
||||
</tr>
|
||||
%s
|
||||
</table>
|
||||
<hr/>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
end
|
||||
|
||||
# response:: parent Ox::Element
|
||||
# stats:: Array of stats
|
||||
# Build propstats response
|
||||
def propstats(response, stats)
|
||||
return if stats.empty?
|
||||
stats.each do |status, props|
|
||||
propstat = Ox::Element.new(D_PROPSTAT)
|
||||
prop = Ox::Element.new(D_PROP)
|
||||
|
||||
props.each do |element, value|
|
||||
|
||||
name = element[:name]
|
||||
if prefix = prefix_for(element[:ns_href])
|
||||
### TODO: A DMSF plugin workaround
|
||||
if prefix =~ /^unknow/
|
||||
next
|
||||
end
|
||||
###
|
||||
name = "#{prefix}:#{name}"
|
||||
end
|
||||
|
||||
prop_element = Ox::Element.new(name)
|
||||
ox_append prop_element, value, prefix: prefix
|
||||
prop << prop_element
|
||||
|
||||
end
|
||||
|
||||
propstat << prop
|
||||
propstat << ox_element(D_STATUS, "#{http_version} #{status.status_line}")
|
||||
|
||||
response << propstat
|
||||
end
|
||||
end
|
||||
|
||||
def options(request, response)
|
||||
return NotFound if ((@path.length > 1) && ((!project) || (!project.module_enabled?('dmsf'))))
|
||||
if @__proxy.read_only
|
||||
response['Allow'] ||= 'OPTIONS,HEAD,GET,PROPFIND'
|
||||
end
|
||||
OK
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def basename
|
||||
File.basename(path)
|
||||
end
|
||||
|
||||
def dirname
|
||||
File.dirname(path)
|
||||
File.basename(@path)
|
||||
end
|
||||
|
||||
# Return instance of Project based on the path
|
||||
@ -185,11 +205,11 @@ module RedmineDmsf
|
||||
|
||||
# Make it easy to find the path without project in it.
|
||||
def projectless_path
|
||||
'/' + path.split('/').drop(2).join('/')
|
||||
'/' + @path.split('/').drop(2).join('/')
|
||||
end
|
||||
|
||||
def path_prefix
|
||||
public_path.gsub(/#{Regexp.escape(path)}$/, '')
|
||||
@public_path.gsub(/#{Regexp.escape(path)}$/, '')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Copyright © 2012 Daniel Munn <dan.munn@munnster.co.uk>
|
||||
# Copyright © 2011-18 Karel Pičman <karel.picman@kontron.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
module RedmineDmsf
|
||||
module Webdav
|
||||
|
||||
class Cache
|
||||
|
||||
def self.read(name, options = nil)
|
||||
Rails.cache.read(name, options) if Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] == '1'
|
||||
end
|
||||
|
||||
def self.write(name, value, options = nil)
|
||||
Rails.cache.write(name, value, options) if Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] == '1'
|
||||
end
|
||||
|
||||
def self.delete(name, options = nil)
|
||||
Rails.cache.delete(name, options) if Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] == '1'
|
||||
end
|
||||
|
||||
def self.exist?(name, options = nil)
|
||||
(Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] == '1') && Rails.cache.exist?(name, options)
|
||||
end
|
||||
|
||||
def self.clear
|
||||
Rails.cache.clear if Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] == '1'
|
||||
end
|
||||
|
||||
def self.invalidate_item(key)
|
||||
if (Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] == '1') && (!key.blank?)
|
||||
# Write an .invalid entry to notify anyone that is currently creating a response
|
||||
self.write("#{key}.invalid", expires_in: 60.seconds)
|
||||
# Delete any existing entry in the cache
|
||||
self.delete(key)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@ -1,250 +0,0 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Copyright © 2012 Daniel Munn <dan.munn@munnster.co.uk>
|
||||
# Copyright © 2011-18 Karel Pičman <karel.picman@kontron.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
require 'dav4rack'
|
||||
require 'addressable/uri'
|
||||
|
||||
module RedmineDmsf
|
||||
module Webdav
|
||||
class Controller < DAV4Rack::Controller
|
||||
include DAV4Rack::Utils
|
||||
|
||||
# Return response to OPTIONS
|
||||
def options
|
||||
# exist? returns false if user is anonymous for ProjectResource and DmsfResource, but not for IndexResource.
|
||||
if resource.exist?
|
||||
# resource exists and user is not anonymous.
|
||||
add_dav_header
|
||||
response['Allow'] = 'OPTIONS,HEAD,GET,PROPFIND'
|
||||
webdav_setting = Setting.plugin_redmine_dmsf['dmsf_webdav_strategy']
|
||||
if webdav_setting && (webdav_setting != 'WEBDAV_READ_ONLY')
|
||||
response['Allow'] << ',PUT,POST,DELETE,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
|
||||
end
|
||||
response['Ms-Author-Via'] = 'DAV'
|
||||
OK
|
||||
elsif resource.really_exist? &&
|
||||
!request.user_agent.nil? && request.user_agent.downcase.include?('microsoft office') &&
|
||||
User.current && User.current.anonymous?
|
||||
# resource actually exist, but this was an anonymous request from MsOffice so respond with 405,
|
||||
# hopefully the resource did actually exist but failed because of anon.
|
||||
# If responding with 401 then MsOffice will fail.
|
||||
# If responding with 200 then MsOffice will think that anonymous access is ok for everything.
|
||||
# Responding with 405 is a workaround found in https://support.microsoft.com/en-us/kb/2019105
|
||||
MethodNotAllowed
|
||||
else
|
||||
# Return NotFound if resource does not exist and the request is not anonymous from MsOffice
|
||||
NotFound
|
||||
end
|
||||
end
|
||||
|
||||
# Return response to HEAD
|
||||
def head
|
||||
# exist? returns false if user is anonymous for ProjectResource and DmsfResource, but not for IndexResource.
|
||||
if resource.exist?
|
||||
# resource exists and user is not anonymous.
|
||||
super
|
||||
elsif resource.really_exist? &&
|
||||
!request.user_agent.nil? && request.user_agent.downcase.include?('microsoft office') &&
|
||||
User.current && User.current.anonymous?
|
||||
# resource said it don't exist, but this was an anonymous request from MsOffice so respond anyway
|
||||
# Can not call super here since it calls resource.exist? which will fail
|
||||
response['Etag'] = resource.etag
|
||||
response['Content-Type'] = resource.content_type
|
||||
response['Last-Modified'] = resource.last_modified.httpdate
|
||||
OK
|
||||
else
|
||||
# Return NotFound if resource does not exist and the request is not anonymous from MsOffice
|
||||
NotFound
|
||||
end
|
||||
end
|
||||
|
||||
# Return response to PROPFIND
|
||||
def propfind
|
||||
return MethodNotAllowed if resource && !resource.public_path.start_with?('/dmsf/webdav')
|
||||
unless resource.exist?
|
||||
NotFound
|
||||
else
|
||||
# Win7 hack start
|
||||
if request_document.xpath("//#{ns}propfind").empty? || request_document.xpath("//#{ns}propfind/#{ns}allprop").present?
|
||||
properties = resource.properties.map { |prop| DAV4Rack::DAVElement.new(prop.merge(:namespace => DAV4Rack::DAVElement.new(:href => prop[:ns_href]))) }
|
||||
# Win7 hack end
|
||||
else
|
||||
check = request_document.xpath("//#{ns}propfind")
|
||||
if(check && !check.empty?)
|
||||
properties = request_document.xpath(
|
||||
"//#{ns}propfind/#{ns}prop"
|
||||
).children.find_all{ |item|
|
||||
item.element?
|
||||
}.map{ |item|
|
||||
# We should do this, but Nokogiri transforms prefix w/ null href into
|
||||
# something valid. Oops.
|
||||
# TODO: Hacky grep fix that's horrible
|
||||
hsh = to_element_hash(item)
|
||||
if(hsh.namespace.nil? && !ns.empty?)
|
||||
raise BadRequest if request_document.to_s.scan(%r{<#{item.name}[^>]+xmlns=""}).empty?
|
||||
end
|
||||
hsh
|
||||
}.compact
|
||||
else
|
||||
raise BadRequest
|
||||
end
|
||||
end
|
||||
|
||||
if depth != 0
|
||||
# Only use cache for requests with a depth>0, depth=0 responses are already fast.
|
||||
pinfo = resource.path.split('/').drop(1)
|
||||
if (pinfo.length == 0) # If this is the base_path, we're at root
|
||||
# Don't know when projects are added/removed from the visibility list for this user,
|
||||
# so don't cache root.
|
||||
elsif (pinfo.length == 1) #This is first level, and as such, project path
|
||||
propfind_key = "PROPFIND/#{resource.resource.project_id}"
|
||||
else # We made it all the way to DMSF Data
|
||||
if resource.collection?
|
||||
# Only store collections in the cache since responses to files are simple and fast already.
|
||||
propfind_key = "PROPFIND/#{resource.resource.project_id}/#{resource.resource.folder.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if propfind_key.nil?
|
||||
# This PROPFIND is never cached so always create a new response
|
||||
create_propfind_response(properties)
|
||||
else
|
||||
response.body = RedmineDmsf::Webdav::Cache.read(propfind_key)
|
||||
if !response.body.nil?
|
||||
# Found cached PROPFIND, fill in Content-Type and Content-Length
|
||||
response['Content-Type'] = 'text/xml; charset="utf-8"'
|
||||
response['Content-Length'] = response.body.size.to_s
|
||||
else
|
||||
# No cached PROPFIND found
|
||||
# Remove .invalid entry for this propfind since we are now creating a new valid propfind
|
||||
RedmineDmsf::Webdav::Cache.delete("#{propfind_key}.invalid")
|
||||
create_propfind_response(properties)
|
||||
|
||||
# Cache response.body, but only if no .invalid entry was stored while creating the propfind
|
||||
RedmineDmsf::Webdav::Cache.write(propfind_key, response.body) unless RedmineDmsf::Webdav::Cache.exist?("#{propfind_key}.invalid")
|
||||
end
|
||||
end
|
||||
# Return HTTP code.
|
||||
MultiStatus
|
||||
end
|
||||
end
|
||||
|
||||
# args:: Only argument used: :copy
|
||||
# Move Resource to new location. If :copy is provided,
|
||||
# Resource will be copied (implementation ease)
|
||||
# The only reason for overriding is a typing mistake 'include' -> 'include?'
|
||||
# and a wrong regular expression for BadGateway check
|
||||
def move(*args)
|
||||
unless(resource.exist?)
|
||||
NotFound
|
||||
else
|
||||
resource.lock_check if resource.supports_locking? && !args.include?(:copy)
|
||||
destination = url_unescape(env['HTTP_DESTINATION'].sub(%r{https?://([^/]+)}, ''))
|
||||
host = $1.gsub(/:\d{2,5}$/, '') if $1
|
||||
host = host.gsub(/^.+@/, '') if host
|
||||
if(host != request.host)
|
||||
BadGateway
|
||||
elsif(destination == resource.public_path)
|
||||
Forbidden
|
||||
else
|
||||
collection = resource.collection?
|
||||
dest = resource_class.new(destination, clean_path(destination), @request, @response, @options.merge(:user => resource.user))
|
||||
status = nil
|
||||
if(args.include?(:copy))
|
||||
status = resource.copy(dest, overwrite)
|
||||
else
|
||||
return Conflict unless depth.is_a?(Symbol) || depth > 1
|
||||
status = resource.move(dest, overwrite)
|
||||
end
|
||||
response['Location'] = "#{scheme}://#{host}:#{port}#{url_format(dest)}" if status == Created
|
||||
status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Escape URL string
|
||||
def url_format(resource)
|
||||
ret = resource.public_path
|
||||
if resource.collection? && (ret[-1,1] != '/')
|
||||
ret += '/'
|
||||
end
|
||||
Addressable::URI.escape ret
|
||||
end
|
||||
|
||||
def url_unescape(str)
|
||||
Addressable::URI.unescape str
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_propfind_response(properties)
|
||||
# Generate response, is stored in response.body
|
||||
render_xml(:multistatus) do |xml|
|
||||
find_resources.each do |resource|
|
||||
if resource.collection?
|
||||
# Index, Project or Folder
|
||||
# path is unique enough for the key and is available for all three, and the path doesn't change
|
||||
# for this path as long as it stays. On its path. The path does not stray from its path without
|
||||
# changing its path.
|
||||
propstats_key = "PROPSTATS#{resource.path}"
|
||||
else
|
||||
# File
|
||||
# Use file.id & file.last_revision.id as key
|
||||
# When revision changes then the key will change and the old cached item will eventually be evicted
|
||||
propstats_key = "PROPSTATS/#{resource.resource.file.id}-#{resource.resource.file.last_revision.id}" if resource.resource.file
|
||||
end
|
||||
|
||||
xml_str = RedmineDmsf::Webdav::Cache.read(propstats_key) if propstats_key
|
||||
if xml_str.nil?
|
||||
# Create the complete PROPSTATS response
|
||||
propstats_builder = Nokogiri::XML::Builder.new do |propstats_xml|
|
||||
propstats_xml.send('propstat', {'xmlns:D' => 'DAV:'}.merge(resource.root_xml_attributes)) do
|
||||
propstats_xml.parent.namespace = propstats_xml.parent.namespace_definitions.first
|
||||
xml2 = propstats_xml['D']
|
||||
|
||||
xml2.response do
|
||||
unless(resource.propstat_relative_path)
|
||||
xml2.href "#{scheme}://#{host}:#{port}#{url_format(resource)}"
|
||||
else
|
||||
xml2.href url_format(resource)
|
||||
end
|
||||
propstats(xml2, get_properties(resource, properties.empty? ? resource.properties : properties))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Just want to add the <:D:response> so extract it.
|
||||
# Q: Is there a better/faster way to do this?
|
||||
xml_str = Nokogiri::XML.parse(propstats_builder.to_xml).xpath('//D:response').first.to_xml
|
||||
|
||||
# Add PROPSTATS to cache
|
||||
# Caching the PROPSTATS response as xml text string.
|
||||
RedmineDmsf::Webdav::Cache.write(propstats_key, xml_str) if propstats_key
|
||||
end
|
||||
xml << xml_str
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
64
lib/redmine_dmsf/webdav/custom_middleware.rb
Normal file
64
lib/redmine_dmsf/webdav/custom_middleware.rb
Normal file
@ -0,0 +1,64 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Copyright © 2011-18 Karel Pičman <karel.picman@kontron.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
require 'dav4rack'
|
||||
|
||||
module RedmineDmsf
|
||||
module Webdav
|
||||
|
||||
class CustomMiddleware
|
||||
|
||||
def initialize(app)
|
||||
@rails_app = app
|
||||
@dav_app = Rack::Builder.new{
|
||||
map '/dmsf/webdav/' do
|
||||
run DAV4Rack::Handler.new(
|
||||
:root_uri_path => File.join(Redmine::Utils::relative_url_root, 'dmsf','webdav'),
|
||||
:resource_class => RedmineDmsf::Webdav::ResourceProxy,
|
||||
:log_to => Rails.logger,
|
||||
:allow_unauthenticated_options_on_root => true
|
||||
)
|
||||
end
|
||||
}.to_app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, body = @dav_app.call env
|
||||
# If the URL map generated by Rack::Builder did not find a matching path,
|
||||
# it will return a 404 along with the X-Cascade header set to 'pass'.
|
||||
if status == 404 and headers['X-Cascade'] == 'pass'
|
||||
# The MS web redirector webdav client likes to go up a level and try
|
||||
# OPTIONS there. We catch that here and respond telling it that just
|
||||
# plain HTTP is going on.
|
||||
if 'OPTIONS'.casecmp(env['REQUEST_METHOD'].to_s) == 0
|
||||
[ '200', { 'Allow' => 'OPTIONS,HEAD,GET,PUT,POST,DELETE' }, [''] ]
|
||||
else
|
||||
# let Rails handle the request
|
||||
@rails_app.call env
|
||||
end
|
||||
else
|
||||
[status, headers, body]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@ -25,11 +25,12 @@ require 'addressable/uri'
|
||||
module RedmineDmsf
|
||||
module Webdav
|
||||
class DmsfResource < BaseResource
|
||||
include Redmine::I18n
|
||||
|
||||
def initialize(*args)
|
||||
def initialize(path, request, response, options)
|
||||
@folder = nil
|
||||
@file = nil
|
||||
super(*args)
|
||||
super(path, request, response, options)
|
||||
end
|
||||
|
||||
# Here we make sure our folder and file methods are not aliased - it should shave a few cycles off of processing
|
||||
@ -37,17 +38,6 @@ module RedmineDmsf
|
||||
@skip_alias |= [ :folder, :file ]
|
||||
end
|
||||
|
||||
# Here we hook into the fact that resources can have a pre-execution routine run for them
|
||||
# Our sole job here is to ensure that any write functionality is restricted to relevent configuration
|
||||
before do |resource, method_name|
|
||||
# If our method is not one of the following, there is no point continuing.
|
||||
if [ :put, :make_collection, :move, :copy, :delete, :lock, :unlock, :set_property ].include?(method_name)
|
||||
webdav_setting = Setting.plugin_redmine_dmsf['dmsf_webdav_strategy']
|
||||
webdav_setting = 'WEBDAV_READ_ONLY' unless webdav_setting
|
||||
raise BadGateway if webdav_setting == 'WEBDAV_READ_ONLY'
|
||||
end
|
||||
end
|
||||
|
||||
# Gather collection of objects that denote current entities child entities
|
||||
# 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
|
||||
@ -69,11 +59,12 @@ module RedmineDmsf
|
||||
|
||||
# Does the object exist?
|
||||
# If it is either a folder or a file, then it exists
|
||||
# - 2012-06-15: Only if you're allowed to browse the project
|
||||
# - 2012-06-18: Issue #5, ensure item is only listed if project is enabled for dmsf
|
||||
def exist?
|
||||
project && project.module_enabled?('dmsf') && (folder || file) &&
|
||||
(User.current.admin? || User.current.allowed_to?(:view_dmsf_folders, project))
|
||||
# # Allow anonymous HEAD requests from MsOffice
|
||||
# return true if @request.request_method.downcase == 'head' && !@request.user_agent.nil? &&
|
||||
# request.user_agent.downcase.include?('microsoft office')
|
||||
self.project && self.project.module_enabled?('dmsf') && (self.folder || self.file) &&
|
||||
(User.current.admin? || User.current.allowed_to?(:view_dmsf_folders, self.project))
|
||||
end
|
||||
|
||||
def really_exist?
|
||||
@ -89,7 +80,7 @@ module RedmineDmsf
|
||||
def folder
|
||||
unless @folder
|
||||
return nil unless project
|
||||
f = parent.folder
|
||||
#f = parent.folder
|
||||
@folder = DmsfFolder.visible.where(:project_id => project.id, :title => basename,
|
||||
:dmsf_folder_id => parent.folder ? parent.folder.id : nil).first
|
||||
end
|
||||
@ -236,12 +227,11 @@ module RedmineDmsf
|
||||
# Behavioural differences between collection and single entity
|
||||
# TODO: Support overwrite between both types of entity, and implement better checking
|
||||
def move(dest, overwrite)
|
||||
dest = @__proxy.class.new(dest, @request, @response, @options.merge(:user => @user))
|
||||
# All of this should carry across the ResourceProxy frontend, we ensure this to
|
||||
# prevent unexpected errors
|
||||
resource = dest.is_a?(ResourceProxy) ? dest.resource : dest
|
||||
|
||||
return PreconditionFailed if !resource.is_a?(DmsfResource) || resource.project.nil?
|
||||
|
||||
parent = resource.parent
|
||||
raise Forbidden unless (!parent.exist? || !parent.folder || DmsfFolder.permissions?(parent.folder, false))
|
||||
|
||||
@ -255,9 +245,6 @@ module RedmineDmsf
|
||||
if dest.exist?
|
||||
MethodNotAllowed
|
||||
else
|
||||
# Must invalidate source parent folder cache before moving
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(folder.propfind_cache_key)
|
||||
|
||||
if(parent.projectless_path == '/') #Project root
|
||||
folder.dmsf_folder_id = nil
|
||||
else
|
||||
@ -322,8 +309,6 @@ module RedmineDmsf
|
||||
else
|
||||
if (project == resource.project) && (file.last_revision.size == 0)
|
||||
# Moving a zero sized file within the same project, just update the dmsf_folder
|
||||
# Must invalidate source parent folder cache before moving
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item(file.propfind_cache_key)
|
||||
file.dmsf_folder = f
|
||||
else
|
||||
return InternalServerError unless file.move_to(resource.project, f)
|
||||
@ -347,8 +332,8 @@ module RedmineDmsf
|
||||
#
|
||||
# Behavioural differences between collection and single entity
|
||||
# TODO: Support overwrite between both types of entity, and an integrative copy where destination exists for collections
|
||||
def copy(dest, overwrite)
|
||||
|
||||
def copy(dest, overwrite, depth)
|
||||
dest = @__proxy.class.new(dest, @request, @response, @options.merge(:user => @user))
|
||||
# All of this should carry across the ResourceProxy frontend, we ensure this to
|
||||
# prevent unexpected errors
|
||||
if dest.is_a?(ResourceProxy)
|
||||
@ -386,7 +371,7 @@ module RedmineDmsf
|
||||
Created
|
||||
else
|
||||
if dest.exist?
|
||||
methodNotAllowed
|
||||
MethodNotAllowed
|
||||
# Files cannot be merged at this point, until a decision is made on how to merge them
|
||||
# ideally, we would merge revision history for both, ensuring the origin file wins with latest revision.
|
||||
else
|
||||
@ -619,71 +604,29 @@ module RedmineDmsf
|
||||
Created
|
||||
end
|
||||
|
||||
# get_property
|
||||
# Overriding the base definition (extending it really) with functionality
|
||||
# for lock information to be presented
|
||||
def get_property(element)
|
||||
raise NotImplemented if (element[:ns_href] != 'DAV:')
|
||||
unless folder
|
||||
return NotFound unless (file && file.last_revision)
|
||||
end
|
||||
case element[:name]
|
||||
when 'supportedlock'
|
||||
supportedlock
|
||||
when 'lockdiscovery'
|
||||
lockdiscovery
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Available properties
|
||||
def properties
|
||||
%w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength supportedlock lockdiscovery).collect do |prop|
|
||||
{:name => prop, :ns_href => 'DAV:'}
|
||||
end
|
||||
end
|
||||
|
||||
def project_id
|
||||
self.project.id if self.project
|
||||
end
|
||||
|
||||
private
|
||||
# Prepare file for download using Rack functionality:
|
||||
# Download (see RedmineDmsf::Webdav::Download) extends Rack::File to allow single-file
|
||||
# implementation of service for request, which allows for us to pipe a single file through
|
||||
# also best-utilising DAV4Rack's implementation.
|
||||
def download
|
||||
raise NotFound unless (file && file.last_revision && file.last_revision.disk_file(false))
|
||||
raise Forbidden unless (!parent.exist? || !parent.folder || DmsfFolder.permissions?(parent.folder))
|
||||
|
||||
# If there is no range (start of ranged download, or direct download) then we log the
|
||||
# file access, so we can properly keep logged information
|
||||
if @request.env['HTTP_RANGE'].nil?
|
||||
access = DmsfFileRevisionAccess.new
|
||||
access.user = User.current
|
||||
access.dmsf_file_revision = file.last_revision
|
||||
access.action = DmsfFileRevisionAccess::DownloadAction
|
||||
access.save!
|
||||
# array of lock info hashes
|
||||
# required keys are :time, :token, :depth
|
||||
# other valid keys are :scope, :type, :root and :owner
|
||||
def lockdiscovery
|
||||
entity = file || folder
|
||||
return [] unless entity.locked?
|
||||
if entity.dmsf_folder && entity.dmsf_folder.locked?
|
||||
entity.lock.reverse[0].folder.locks(false) # longwinded way of getting base items locks
|
||||
else
|
||||
entity.lock(false)
|
||||
end
|
||||
Download.new(file.last_revision.disk_file)
|
||||
end
|
||||
|
||||
# As the name suggests, we're returning lock recovery information for requested resource
|
||||
def lockdiscovery
|
||||
# returns an array of activelock ox elements
|
||||
def lockdiscovery_xml
|
||||
x = Nokogiri::XML::DocumentFragment.parse ''
|
||||
entity = file || folder
|
||||
return nil unless entity.locked?
|
||||
|
||||
if entity.dmsf_folder && entity.dmsf_folder.locked?
|
||||
locks = entity.lock.reverse[0].folder.locks(false) # longwinded way of getting base items locks
|
||||
else
|
||||
locks = entity.lock(false)
|
||||
end
|
||||
|
||||
Nokogiri::XML::Builder.with(x) do |doc|
|
||||
doc.lockdiscovery {
|
||||
locks.each do |lock|
|
||||
lockdiscovery.each do |lock|
|
||||
next if lock.expired?
|
||||
doc.activelock {
|
||||
doc.locktype { doc.write }
|
||||
@ -716,22 +659,24 @@ module RedmineDmsf
|
||||
x
|
||||
end
|
||||
|
||||
# As the name suggests, we're returning locks supported by our implementation
|
||||
def supportedlock
|
||||
x = Nokogiri::XML::DocumentFragment.parse ''
|
||||
Nokogiri::XML::Builder.with(x) do |doc|
|
||||
doc.supportedlock {
|
||||
doc.lockentry {
|
||||
doc.lockscope { doc.exclusive }
|
||||
doc.locktype { doc.write }
|
||||
}
|
||||
doc.lockentry {
|
||||
doc.lockscope { doc.shared }
|
||||
doc.locktype { doc.write }
|
||||
}
|
||||
}
|
||||
private
|
||||
# Prepare file for download using Rack functionality:
|
||||
# Download (see RedmineDmsf::Webdav::Download) extends Rack::File to allow single-file
|
||||
# implementation of service for request, which allows for us to pipe a single file through
|
||||
# also best-utilising DAV4Rack's implementation.
|
||||
def download
|
||||
raise NotFound unless (file && file.last_revision && file.last_revision.disk_file(false))
|
||||
raise Forbidden unless (!parent.exist? || !parent.folder || DmsfFolder.permissions?(parent.folder))
|
||||
# If there is no range (start of ranged download, or direct download) then we log the
|
||||
# file access, so we can properly keep logged information
|
||||
if @request.env['HTTP_RANGE'].nil?
|
||||
access = DmsfFileRevisionAccess.new
|
||||
access.user = User.current
|
||||
access.dmsf_file_revision = file.last_revision
|
||||
access.action = DmsfFileRevisionAccess::DownloadAction
|
||||
access.save!
|
||||
end
|
||||
x
|
||||
File.new(file.last_revision.disk_file)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Redmine plugin for Document Management System "Features"
|
||||
#
|
||||
# Copyright © 2012 Daniel Munn <dan.munn@munnster.co.uk>
|
||||
# Copyright © 2011-18 Karel Pičman <karel.picman@kontron.com>
|
||||
#
|
||||
# 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.
|
||||
|
||||
require 'time'
|
||||
require 'rack/utils'
|
||||
require 'rack/mime'
|
||||
|
||||
module RedmineDmsf
|
||||
module Webdav
|
||||
class Download < Rack::File
|
||||
def initialize(root, cache_control = nil)
|
||||
@path = root
|
||||
@cache_control = cache_control
|
||||
end
|
||||
|
||||
def _call(env)
|
||||
unless ALLOWED_VERBS.include? env['REQUEST_METHOD']
|
||||
return fail(405, 'Method Not Allowed')
|
||||
end
|
||||
|
||||
available = begin
|
||||
F.file?(@path) && F.readable?(@path)
|
||||
rescue SystemCallError
|
||||
false
|
||||
end
|
||||
|
||||
if available
|
||||
serving(env)
|
||||
else
|
||||
fail(404, "File not found: #{@path}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -23,8 +23,8 @@ module RedmineDmsf
|
||||
module Webdav
|
||||
class IndexResource < BaseResource
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
def initialize(path, request, response, options)
|
||||
super(path, request, response, options)
|
||||
@projects = nil
|
||||
end
|
||||
|
||||
|
||||
@ -22,9 +22,10 @@
|
||||
module RedmineDmsf
|
||||
module Webdav
|
||||
class ProjectResource < BaseResource
|
||||
include Redmine::I18n
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
def initialize(path, request, response, options)
|
||||
super path, request, response, options
|
||||
@children = nil
|
||||
end
|
||||
|
||||
@ -104,13 +105,6 @@ module RedmineDmsf
|
||||
def file
|
||||
nil
|
||||
end
|
||||
|
||||
# Available properties
|
||||
def properties
|
||||
%w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength supportedlock lockdiscovery).collect do |prop|
|
||||
{:name => prop, :ns_href => 'DAV:'}
|
||||
end
|
||||
end
|
||||
|
||||
def project_id
|
||||
self.project.id if self.project
|
||||
@ -122,7 +116,7 @@ module RedmineDmsf
|
||||
def self.create_project_name(p)
|
||||
use_project_names = Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names']
|
||||
if use_project_names
|
||||
# 1. Invalid characters are replaced with a dot.
|
||||
# 1. Invalid characters are replaced with dots.
|
||||
# 2. Two or more dots in a row are replaced with a single dot.
|
||||
# (3. Windows WebClient does not like a dot at the end, but since the project id tag is appended this is not a problem.)
|
||||
"#{p.name.gsub(INVALID_CHARACTERS, '.').gsub(/\.{2,}/, '.')} #{p.id}" if p
|
||||
|
||||
@ -31,42 +31,41 @@ module RedmineDmsf
|
||||
# object, and proxies calls made against class to it.
|
||||
class ResourceProxy < DAV4Rack::Resource
|
||||
|
||||
attr_reader :read_only
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
pinfo = path.split('/').drop(1)
|
||||
if (pinfo.length == 0) # If this is the base_path, we're at root
|
||||
@resource_c = IndexResource.new(*args)
|
||||
elsif (pinfo.length == 1) #This is first level, and as such, project path
|
||||
elsif (pinfo.length == 1) # This is the first level, and as such, project path
|
||||
@resource_c = ProjectResource.new(*args)
|
||||
else # We made it all the way to DMSF Data
|
||||
@resource_c = DmsfResource.new(*args)
|
||||
end
|
||||
@resource_c.accessor = self if @resource_c
|
||||
@read_only = Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] == 'WEBDAV_READ_ONLY'
|
||||
end
|
||||
|
||||
def authenticate(username, password)
|
||||
unless username && password
|
||||
# Bugfix: Current DAV4Rack (including production) authenticate against ALL requests
|
||||
# Microsoft Web Client will not attempt any authentication (it'd seem) until it's acknowledged
|
||||
# a completed OPTIONS request. Ideally this is a flaw with the controller, however as I'm not
|
||||
# going to fork it to ensure compliance, checking the request method in the authentication
|
||||
# seems the next best step, if the request method is OPTIONS return true, controller will simply
|
||||
# call the options method within, which accesses nothing, just returns headers about dav env.
|
||||
return true if @request.request_method.downcase == 'options' && (path == '/' || path.empty?)
|
||||
|
||||
# Allow anonymous OPTIONS requests from MsOffice
|
||||
return true if @request.request_method.downcase == 'options' && !@request.user_agent.nil? && @request.user_agent.downcase.include?('microsoft office')
|
||||
# Allow anonymous HEAD requests from MsOffice
|
||||
return true if @request.request_method.downcase == 'head' && !@request.user_agent.nil? && request.user_agent.downcase.include?('microsoft office')
|
||||
end
|
||||
|
||||
return false unless username && password
|
||||
User.current = User.try_to_login(username, password)
|
||||
return User.current && !User.current.anonymous?
|
||||
end
|
||||
|
||||
def options(request, response)
|
||||
@resource_c.options request, response
|
||||
end
|
||||
|
||||
def supports_locking?
|
||||
true
|
||||
!@read_only
|
||||
end
|
||||
|
||||
def lockdiscovery
|
||||
@resource_c.lockdiscovery
|
||||
end
|
||||
|
||||
def lockdiscovery_xml
|
||||
@resource_c.lockdiscovery_xml
|
||||
end
|
||||
|
||||
def children
|
||||
@ -110,22 +109,27 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
def put(request, response)
|
||||
raise BadGateway if @read_only
|
||||
@resource_c.put(request, response)
|
||||
end
|
||||
|
||||
def delete
|
||||
raise BadGateway if @read_only
|
||||
@resource_c.delete
|
||||
end
|
||||
|
||||
def copy(dest, overwrite = false)
|
||||
@resource_c.copy(dest, overwrite)
|
||||
def copy(dest, overwrite, depth)
|
||||
raise BadGateway if @read_only
|
||||
@resource_c.copy(dest, overwrite, depth)
|
||||
end
|
||||
|
||||
def move(dest, overwrite = false)
|
||||
raise BadGateway if @read_only
|
||||
@resource_c.move(dest, overwrite)
|
||||
end
|
||||
|
||||
def make_collection
|
||||
raise BadGateway if @read_only
|
||||
@resource_c.make_collection
|
||||
end
|
||||
|
||||
@ -134,6 +138,7 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
def lock(args)
|
||||
raise BadGateway if @read_only
|
||||
@resource_c.lock(args)
|
||||
end
|
||||
|
||||
@ -142,6 +147,7 @@ module RedmineDmsf
|
||||
end
|
||||
|
||||
def unlock(token)
|
||||
raise BadGateway if @read_only
|
||||
@resource_c.unlock(token)
|
||||
end
|
||||
|
||||
@ -164,6 +170,10 @@ module RedmineDmsf
|
||||
def properties
|
||||
@resource_c.properties
|
||||
end
|
||||
|
||||
def propstats(response, stats)
|
||||
@resource_c.propstats(response, stats)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -18,27 +18,22 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
desc <<-END_DESC
|
||||
Clears the Webdav Cache
|
||||
require File.expand_path('../../../test_helper', __FILE__)
|
||||
|
||||
Example:
|
||||
rake redmine:dmsf_clear_webdav_cache RAILS_ENV="production"
|
||||
END_DESC
|
||||
|
||||
namespace :redmine do
|
||||
task :dmsf_clear_webdav_cache => :environment do
|
||||
DmsfWebdavCache.clear
|
||||
end
|
||||
end
|
||||
class DmsfWebdavCustomMiddlewareTest < RedmineDmsf::Test::IntegrationTest
|
||||
|
||||
class DmsfWebdavCache
|
||||
def self.clear
|
||||
if Setting.plugin_redmine_dmsf['dmsf_memcached_servers'].nil? || Setting.plugin_redmine_dmsf['dmsf_memcached_servers'].empty?
|
||||
puts 'No memcached server configured.'
|
||||
else
|
||||
puts "Clearing memcached at '#{Setting.plugin_redmine_dmsf['dmsf_memcached_servers']}'"
|
||||
cache = ActiveSupport::Cache::MemCacheStore.new(Setting.plugin_redmine_dmsf['dmsf_memcached_servers'])
|
||||
cache.clear
|
||||
end
|
||||
def setup
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav'] = '1'
|
||||
end
|
||||
|
||||
def test_options_for_root_path
|
||||
xml_http_request :options, '/'
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_options_for_dmsf_root_path
|
||||
xml_http_request :options, '/dmsf'
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
end
|
||||
@ -245,7 +245,7 @@ class DmsfWebdavDeleteTest < RedmineDmsf::Test::IntegrationTest
|
||||
@file1.reload
|
||||
assert !@file1.deleted?, "File #{@file1.name} is expected to exist"
|
||||
end
|
||||
|
||||
|
||||
def test_non_versioned_file
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_disable_versioning'] = '\.tmp$'
|
||||
if Setting.plugin_redmine_dmsf['dmsf_webdav_disable_versioning'] == '\.tmp$'
|
||||
|
||||
@ -30,6 +30,7 @@ class DmsfWebdavHeadTest < RedmineDmsf::Test::IntegrationTest
|
||||
@admin = credentials 'admin'
|
||||
@jsmith = credentials 'jsmith'
|
||||
@project1 = Project.find_by_id 1
|
||||
@project1.enable_module!('dmsf')
|
||||
@project2 = Project.find_by_id 2
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav'] = '1'
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] = 'WEBDAV_READ_WRITE'
|
||||
@ -41,7 +42,7 @@ class DmsfWebdavHeadTest < RedmineDmsf::Test::IntegrationTest
|
||||
User.current = nil
|
||||
end
|
||||
|
||||
def test_truth
|
||||
def test_truth
|
||||
assert_kind_of Project, @project1
|
||||
assert_kind_of Project, @project2
|
||||
end
|
||||
@ -56,7 +57,7 @@ class DmsfWebdavHeadTest < RedmineDmsf::Test::IntegrationTest
|
||||
head "/dmsf/webdav/#{@project1.identifier}", nil, @admin
|
||||
assert_response :success
|
||||
check_headers_exist
|
||||
|
||||
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = true
|
||||
if Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] == true
|
||||
head "/dmsf/webdav/#{@project1.identifier}", nil, @admin
|
||||
@ -66,36 +67,16 @@ class DmsfWebdavHeadTest < RedmineDmsf::Test::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
def test_head_responds_anonymous_msoffice_user_agent
|
||||
head "/dmsf/webdav/#{@project1.identifier}", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
assert_response :success
|
||||
check_headers_exist
|
||||
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = true
|
||||
if Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] == true
|
||||
head "/dmsf/webdav/#{@project1.identifier}", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
assert_response 404
|
||||
head "/dmsf/webdav/#{@project1_uri}", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
|
||||
def test_head_responds_anonymous_other_user_agent
|
||||
head "/dmsf/webdav/#{@project1.identifier}", nil, {:HTTP_USER_AGENT => 'Other'}
|
||||
assert_response 401
|
||||
check_headers_dont_exist
|
||||
end
|
||||
|
||||
# Note:
|
||||
# At present we use Rack to serve the file, this makes life easy however it removes the Etag
|
||||
# header and invalidates the test - where as a folder listing will always not include a last-modified
|
||||
# (but may include an etag, so there is an allowance for a 1 in 2 failure rate on (optionally) required
|
||||
# At present we use Rack to serve the file, this makes life easy however it removes the Etag
|
||||
# header and invalidates the test - where as a folder listing will always not include a last-modified
|
||||
# (but may include an etag, so there is an allowance for a 1 in 2 failure rate on (optionally) required
|
||||
# headers)
|
||||
def test_head_responds_to_file
|
||||
def test_head_responds_to_file
|
||||
head "/dmsf/webdav/#{@project1.identifier}/test.txt", nil, @admin
|
||||
assert_response :success
|
||||
check_headers_exist # Note it'll allow 1 out of the 3 expected to fail
|
||||
|
||||
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = true
|
||||
if Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] == true
|
||||
head "/dmsf/webdav/#{@project1.identifier}/test.txt", nil, @admin
|
||||
@ -105,19 +86,19 @@ class DmsfWebdavHeadTest < RedmineDmsf::Test::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
def test_head_responds_to_file_anonymous_msoffice_user_agent
|
||||
head "/dmsf/webdav/#{@project1.identifier}/test.txt", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
assert_response :success
|
||||
check_headers_exist # Note it'll allow 1 out of the 3 expected to fail
|
||||
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = true
|
||||
if Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] == true
|
||||
head "/dmsf/webdav/#{@project1.identifier}/test.txt", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
assert_response 404
|
||||
head "/dmsf/webdav/#{@project1_uri}/test.txt", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
# def test_head_responds_to_file_anonymous_msoffice_user_agent
|
||||
# head "/dmsf/webdav/#{@project1.identifier}/test.txt", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
# assert_response :success
|
||||
# check_headers_exist # Note it'll allow 1 out of the 3 expected to fail
|
||||
#
|
||||
# Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = true
|
||||
# if Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] == true
|
||||
# head "/dmsf/webdav/#{@project1.identifier}/test.txt", nil,{:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
# assert_response 404
|
||||
# head "/dmsf/webdav/#{@project1_uri}/test.txt", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
# assert_response :success
|
||||
# end
|
||||
# end
|
||||
|
||||
def test_head_responds_to_file_anonymous_other_user_agent
|
||||
head "/dmsf/webdav/#{@project1.identifier}/test.txt", nil, {:HTTP_USER_AGENT => 'Other'}
|
||||
@ -130,30 +111,30 @@ class DmsfWebdavHeadTest < RedmineDmsf::Test::IntegrationTest
|
||||
assert_response :missing
|
||||
check_headers_dont_exist
|
||||
end
|
||||
|
||||
def test_head_fails_when_file_not_found_anonymous_msoffice_user_agent
|
||||
head "/dmsf/webdav/#{@project1.identifier}/not_here.txt", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
assert_response :missing
|
||||
check_headers_dont_exist
|
||||
end
|
||||
|
||||
|
||||
# def test_head_fails_when_file_not_found_anonymous_msoffice_user_agent
|
||||
# head "/dmsf/webdav/#{@project1.identifier}/not_here.txt", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
# assert_response :missing
|
||||
# check_headers_dont_exist
|
||||
# end
|
||||
|
||||
def test_head_fails_when_file_not_found_anonymous_other_user_agent
|
||||
head "/dmsf/webdav/#{@project1.identifier}/not_here.txt", nil, {:HTTP_USER_AGENT => 'Other'}
|
||||
assert_response 401
|
||||
check_headers_dont_exist
|
||||
end
|
||||
|
||||
|
||||
def test_head_fails_when_folder_not_found
|
||||
head '/dmsf/webdav/folder_not_here', nil, @admin
|
||||
assert_response :missing
|
||||
check_headers_dont_exist
|
||||
end
|
||||
|
||||
def test_head_fails_when_folder_not_found_anonymous_msoffice_user_agent
|
||||
head '/dmsf/webdav/folder_not_here', nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
assert_response :missing
|
||||
check_headers_dont_exist
|
||||
end
|
||||
# def test_head_fails_when_folder_not_found_anonymous_msoffice_user_agent
|
||||
# head '/dmsf/webdav/folder_not_here', nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
# assert_response :missing
|
||||
# check_headers_dont_exist
|
||||
# end
|
||||
|
||||
def test_head_fails_when_folder_not_found_anonymous_other_user_agent
|
||||
head '/dmsf/webdav/folder_not_here', nil, {:HTTP_USER_AGENT => 'Other'}
|
||||
@ -168,9 +149,9 @@ class DmsfWebdavHeadTest < RedmineDmsf::Test::IntegrationTest
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
def check_headers_exist
|
||||
assert !(response.headers.nil? || response.headers.empty?),
|
||||
assert !(response.headers.nil? || response.headers.empty?),
|
||||
'Head returned without headers' # Headers exist?
|
||||
values = {}
|
||||
values[:etag] = { :optional => true, :content => response.headers['Etag'] }
|
||||
|
||||
@ -74,25 +74,38 @@ class DmsfWebdavMoveTest < RedmineDmsf::Test::IntegrationTest
|
||||
# Time travel, will make the usec part of the time 0
|
||||
travel_to create_time do
|
||||
# Lock file
|
||||
xml_http_request :lock, "/dmsf/webdav/#{@project1.identifier}/#{file.name}",
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>
|
||||
<d:lockinfo xmlns:d=\"DAV:\">
|
||||
xml = %{<?xml version="1.0" encoding="utf-8" ?>
|
||||
<d:lockinfo xmlns:d="DAV:">
|
||||
<d:lockscope><d:exclusive/></d:lockscope>
|
||||
<d:locktype><d:write/></d:locktype>
|
||||
<d:owner>jsmith</d:owner>
|
||||
</d:lockinfo>",
|
||||
@jsmith.merge!({:HTTP_DEPTH => 'infinity',
|
||||
:HTTP_TIMEOUT => 'Infinite',})
|
||||
</d:lockinfo>}
|
||||
xml_http_request :lock, "/dmsf/webdav/#{@project1.identifier}/#{file.name}", xml,
|
||||
@jsmith.merge!({:HTTP_DEPTH => 'infinity', :HTTP_TIMEOUT => 'Infinite',})
|
||||
assert_response :success
|
||||
# Verify the response
|
||||
assert_match '<D:lockscope><D:exclusive/></D:lockscope>', response.body
|
||||
assert_match '<D:locktype><D:write/></D:locktype>', response.body
|
||||
assert_match '<D:depth>infinity</D:depth>', response.body
|
||||
# <?xml version=\"1.0\"?>
|
||||
# <d:prop xmlns:d=\"DAV:\">
|
||||
# <d:lockdiscovery>
|
||||
# <d:activelock>
|
||||
# <d:lockscope>exclusive</d:lockscope>
|
||||
# <d:locktype>write</d:locktype>
|
||||
# <d:depth>infinity</d:depth>
|
||||
# <d:timeout>Second-604800</d:timeout>
|
||||
# <d:locktoken>
|
||||
# <d:href>f5762389-6b49-4482-9a4b-ff1c8f975765</d:href>
|
||||
# </d:locktoken>
|
||||
# </d:activelock>
|
||||
# </d:lockdiscovery>
|
||||
# </d:prop>
|
||||
assert_match '<d:lockscope>exclusive</d:lockscope>', response.body
|
||||
assert_match '<d:locktype>write</d:locktype>', response.body
|
||||
assert_match '<d:depth>infinity</d:depth>', response.body
|
||||
# 1.week = 7*24*3600=604800 seconds
|
||||
assert_match '<D:timeout>Second-604800</D:timeout>', response.body
|
||||
assert_match(/<D:locktoken><D:href>([a-z0-9\-]+)<\/D:href><\/D:locktoken>/, response.body)
|
||||
assert_match '<d:timeout>Second-604800</d:timeout>', response.body
|
||||
assert_match(/<d:locktoken><d:href>([a-z0-9\-]+)<\/d:href><\/d:locktoken>/, response.body)
|
||||
# Extract the locktoken, needed when refreshing the lock
|
||||
response.body.match(/<D:locktoken><D:href>([a-z0-9\-]+)<\/D:href><\/D:locktoken>/)
|
||||
response.body.match(/<d:locktoken><d:href>([a-z0-9\-]+)<\/d:href><\/d:locktoken>/)
|
||||
locktoken=$1
|
||||
# Verify the lock in the db
|
||||
l = DmsfFile.find_by_id(1).lock.first
|
||||
@ -109,7 +122,7 @@ class DmsfWebdavMoveTest < RedmineDmsf::Test::IntegrationTest
|
||||
:HTTP_IF => "(#{locktoken})"})
|
||||
assert_response :success
|
||||
# 1.week = 7*24*3600=604800 seconds
|
||||
assert_match '<D:timeout>Second-604800</D:timeout>', response.body
|
||||
assert_match '<d:timeout>Second-604800</d:timeout>', response.body
|
||||
# Verify the lock in the db
|
||||
l = DmsfFile.find_by_id(1).lock.first
|
||||
assert_equal l.created_at, create_time
|
||||
|
||||
@ -113,15 +113,6 @@ class DmsfWebdavMoveTest < RedmineDmsf::Test::IntegrationTest
|
||||
assert_response 404 # NotFound
|
||||
end
|
||||
|
||||
def test_move_wrong_destination
|
||||
new_name = "#{@file1.name}.moved"
|
||||
assert_no_difference '@file1.dmsf_file_revisions.count' do
|
||||
xml_http_request :move, "/dmsf/webdav/#{@project1.identifier}/#{@file1.name}", nil,
|
||||
@jsmith.merge!({:destination => "http://www.wrong-url.com/dmsf/webdav/#{@project1.identifier}/#{new_name}"})
|
||||
assert_response 502 # BadGateway
|
||||
end
|
||||
end
|
||||
|
||||
def test_move_to_new_filename
|
||||
if Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] == 'WEBDAV_READ_WRITE'
|
||||
new_name = "#{@file1.name}.moved"
|
||||
|
||||
@ -48,24 +48,20 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
|
||||
|
||||
def test_options_returns_expected_allow_header_for_ro
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] = 'WEBDAV_READ_ONLY'
|
||||
if Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] == 'WEBDAV_READ_ONLY'
|
||||
xml_http_request :options, '/dmsf/webdav'
|
||||
assert_response :success
|
||||
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
|
||||
assert response.headers['Allow'] , 'Allow header is empty or does not exist'
|
||||
assert_equal response.headers['Allow'], 'OPTIONS,HEAD,GET,PROPFIND'
|
||||
end
|
||||
xml_http_request :options, '/dmsf/webdav'
|
||||
assert_response :success
|
||||
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
|
||||
assert response.headers['Allow'] , 'Allow header is empty or does not exist'
|
||||
assert_equal response.headers['Allow'], 'OPTIONS,HEAD,GET,PROPFIND'
|
||||
end
|
||||
|
||||
def test_options_returns_expected_allow_header_for_rw
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] = 'WEBDAV_READ_WRITE'
|
||||
if Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] == 'WEBDAV_READ_WRITE'
|
||||
xml_http_request :options, '/dmsf/webdav'
|
||||
assert_response :success
|
||||
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
|
||||
assert response.headers['Allow'] , 'Allow header is empty or does not exist'
|
||||
assert_equal response.headers['Allow'], 'OPTIONS,HEAD,GET,PROPFIND,PUT,POST,DELETE,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
|
||||
end
|
||||
xml_http_request :options, '/dmsf/webdav'
|
||||
assert_response :success
|
||||
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
|
||||
assert response.headers['Allow'] , 'Allow header is empty or does not exist'
|
||||
assert_equal response.headers['Allow'],
|
||||
'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
|
||||
end
|
||||
|
||||
def test_options_returns_expected_dav_header
|
||||
@ -110,25 +106,23 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
|
||||
end
|
||||
|
||||
def test_authenticated_options_returns_expected_allow_header
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] = 'WEBDAV_READ_WRITE'
|
||||
if Setting.plugin_redmine_dmsf['dmsf_webdav_strategy'] == 'WEBDAV_READ_WRITE'
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, @admin
|
||||
assert_response :success
|
||||
assert !(response.headers.nil? || response.headers.empty?), "Response headers are empty"
|
||||
assert response.headers['Allow'], 'Allow header is empty or does not exist'
|
||||
assert_equal response.headers['Allow'], 'OPTIONS,HEAD,GET,PROPFIND,PUT,POST,DELETE,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
|
||||
end
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, @jsmith
|
||||
assert_response :success
|
||||
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
|
||||
assert response.headers['Allow'], 'Allow header is empty or does not exist'
|
||||
assert_equal response.headers['Allow'],
|
||||
'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
|
||||
end
|
||||
|
||||
def test_authenticated_options_returns_expected_dav_header
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, @admin
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, @jsmith
|
||||
assert_response :success
|
||||
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
|
||||
assert response.headers['Dav'], 'Dav header is empty or does not exist'
|
||||
end
|
||||
|
||||
def test_authenticated_options_returns_expected_ms_auth_via_header
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, @admin
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, @jsmith
|
||||
assert_response :success
|
||||
assert !(response.headers.nil? || response.headers.empty?), 'Response headers are empty'
|
||||
assert response.headers['Ms-Author-Via'], 'Ms-Author-Via header is empty or does not exist'
|
||||
@ -137,11 +131,12 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
|
||||
|
||||
def test_un_authenticated_options_for_msoffice_user_agent
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, {:HTTP_USER_AGENT => 'Microsoft Office Word 2014'}
|
||||
assert_response 405
|
||||
assert_response 401
|
||||
end
|
||||
|
||||
def test_authenticated_options_for_msoffice_user_agent
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_USER_AGENT => 'Microsoft Office Word 2014'})
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil,
|
||||
@admin.merge!({:HTTP_USER_AGENT => 'Microsoft Office Word 2014'})
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
@ -153,15 +148,12 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
|
||||
def test_authenticated_options_for_other_user_agent
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_USER_AGENT => 'Other'})
|
||||
assert_response :success
|
||||
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = true
|
||||
if Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] == true
|
||||
project1_uri = Addressable::URI.escape(RedmineDmsf::Webdav::ProjectResource.create_project_name(@project1))
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_USER_AGENT => 'Other'})
|
||||
assert_response 404
|
||||
xml_http_request :options, "/dmsf/webdav/#{project1_uri}", nil, @admin.merge!({:HTTP_USER_AGENT => 'Other'})
|
||||
assert_response :success
|
||||
end
|
||||
project1_uri = Addressable::URI.escape(RedmineDmsf::Webdav::ProjectResource.create_project_name(@project1))
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_USER_AGENT => 'Other'})
|
||||
assert_response 404
|
||||
xml_http_request :options, "/dmsf/webdav/#{project1_uri}", nil, @admin.merge!({:HTTP_USER_AGENT => 'Other'})
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_authenticated_options_returns_404_for_non_dmsf_enabled_items
|
||||
@ -169,7 +161,7 @@ class DmsfWebdavOptionsTest < RedmineDmsf::Test::IntegrationTest
|
||||
xml_http_request :options, "/dmsf/webdav/#{@project2.identifier}", nil, @jsmith
|
||||
assert_response 404
|
||||
end
|
||||
|
||||
|
||||
def test_authenticated_options_returns_404_for_not_found
|
||||
xml_http_request :options, '/dmsf/webdav/does-not-exist', nil, @jsmith
|
||||
assert_response 404
|
||||
|
||||
@ -47,9 +47,8 @@ class DmsfWebdavPropfindTest < RedmineDmsf::Test::IntegrationTest
|
||||
@project1_uri = Addressable::URI.escape(@project1_name)
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = false
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] = '1'
|
||||
RedmineDmsf::Webdav::Cache.clear
|
||||
end
|
||||
|
||||
|
||||
def test_truth
|
||||
assert_kind_of Project, @project1
|
||||
assert_kind_of Project, @project2
|
||||
@ -63,43 +62,43 @@ class DmsfWebdavPropfindTest < RedmineDmsf::Test::IntegrationTest
|
||||
def test_propfind_depth0_on_root_for_non_member
|
||||
xml_http_request :propfind, '/dmsf/webdav/', nil, @jsmith.merge!({:HTTP_DEPTH => '0'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?('<D:href>http://www.example.com:80/dmsf/webdav/</D:href>')
|
||||
assert response.body.include?('<D:displayname>/</D:displayname>')
|
||||
assert response.body.include?('<d:href>http://www.example.com:80/dmsf/webdav/</d:href>')
|
||||
assert response.body.include?('<d:displayname>/</d:displayname>')
|
||||
end
|
||||
|
||||
def test_propfind_depth1_on_root_for_non_member
|
||||
xml_http_request :propfind, '/dmsf/webdav/', nil, @jsmith.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?('<D:href>http://www.example.com:80/dmsf/webdav/</D:href>')
|
||||
assert response.body.include?( '<D:displayname>/</D:displayname>')
|
||||
assert response.body.include?('<d:href>http://www.example.com:80/dmsf/webdav/</d:href>')
|
||||
assert response.body.include?( '<d:displayname>/</d:displayname>')
|
||||
end
|
||||
|
||||
def test_propfind_depth0_on_root_for_admin
|
||||
xml_http_request :propfind, '/dmsf/webdav/', nil, @admin.merge!({:HTTP_DEPTH => '0'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?('<D:href>http://www.example.com:80/dmsf/webdav/</D:href>')
|
||||
assert response.body.include?('<D:displayname>/</D:displayname>')
|
||||
assert response.body.include?('<d:href>http://www.example.com:80/dmsf/webdav/</d:href>')
|
||||
assert response.body.include?('<d:displayname>/</d:displayname>')
|
||||
end
|
||||
|
||||
def test_propfind_depth1_on_root_for_admin
|
||||
xml_http_request :propfind, '/dmsf/webdav/', nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?('<D:href>http://www.example.com:80/dmsf/webdav/</D:href>')
|
||||
assert response.body.include?('<D:displayname>/</D:displayname>')
|
||||
assert response.body.include?('<d:href>http://www.example.com:80/dmsf/webdav/</d:href>')
|
||||
assert response.body.include?('<d:displayname>/</d:displayname>')
|
||||
end
|
||||
|
||||
def test_propfind_depth1_on_root_for_admin_with_project_names
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_use_project_names'] = true
|
||||
xml_http_request :propfind, '/dmsf/webdav/', nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?('<D:href>http://www.example.com:80/dmsf/webdav/</D:href>')
|
||||
assert response.body.include?('<D:displayname>/</D:displayname>')
|
||||
assert response.body.include?('<d:href>http://www.example.com:80/dmsf/webdav/</d:href>')
|
||||
assert response.body.include?('<d:displayname>/</d:displayname>')
|
||||
# project.identifier should not match when using project names
|
||||
assert !response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</D:href>")
|
||||
assert !response.body.include?("<D:displayname>#{@project1.identifier}</D:displayname>")
|
||||
assert !response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</d:href>")
|
||||
assert !response.body.include?("<d:displayname>#{@project1.identifier}</d:displayname>")
|
||||
# but the project name should match
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@project1_name}</D:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@project1_name}</d:displayname>")
|
||||
end
|
||||
|
||||
def test_propfind_depth0_on_project1_for_non_member
|
||||
@ -110,8 +109,8 @@ class DmsfWebdavPropfindTest < RedmineDmsf::Test::IntegrationTest
|
||||
def test_propfind_depth0_on_project1_for_admin
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '0'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@project1.identifier}</D:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@project1.identifier}</d:displayname>")
|
||||
end
|
||||
|
||||
def test_propfind_depth0_on_project1_for_admin_with_project_names
|
||||
@ -120,28 +119,28 @@ class DmsfWebdavPropfindTest < RedmineDmsf::Test::IntegrationTest
|
||||
assert_response :missing
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1_uri}", nil, @admin.merge!({:HTTP_DEPTH => '0'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@project1_name}</D:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@project1_name}</d:displayname>")
|
||||
end
|
||||
|
||||
def test_propfind_depth1_on_project1_for_admin
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
# Project
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@project1.identifier}</D:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@project1.identifier}</d:displayname>")
|
||||
# Folders
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@folder1.title}/</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@folder1.title}</D:displayname>")
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@folder6.title}/</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@folder6.title}</D:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@folder1.title}/</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@folder1.title}</d:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@folder6.title}/</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@folder6.title}</d:displayname>")
|
||||
# Files
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file1.name}</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@file1.name}</D:displayname>")
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file9.name}</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@file9.name}</D:displayname>")
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file10.name}</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@file10.name}</D:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file1.name}</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@file1.name}</d:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file9.name}</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@file9.name}</d:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file10.name}</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@file10.name}</d:displayname>")
|
||||
end
|
||||
|
||||
def test_propfind_depth1_on_project1_for_admin_with_project_names
|
||||
@ -151,183 +150,60 @@ class DmsfWebdavPropfindTest < RedmineDmsf::Test::IntegrationTest
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1_uri}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
# Project
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/</D:href>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/</d:href>")
|
||||
# Folders
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/#{@folder1.title}/</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@folder1.title}</D:displayname>")
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/#{@folder6.title}/</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@folder6.title}</D:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/#{@folder1.title}/</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@folder1.title}</d:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/#{@folder6.title}/</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@folder6.title}</d:displayname>")
|
||||
# Files
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/#{@file1.name}</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@file1.name}</D:displayname>")
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/#{@file9.name}</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@file9.name}</D:displayname>")
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/#{@file10.name}</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{@file10.name}</D:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/#{@file1.name}</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@file1.name}</d:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/#{@file9.name}</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@file9.name}</d:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1_uri}/#{@file10.name}</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{@file10.name}</d:displayname>")
|
||||
end
|
||||
|
||||
def test_propfind_depth1_on_root_for_admin_with_project_names_and_cache
|
||||
def test_propfind_depth1_on_root_for_admin_with_project_names
|
||||
@project1.name = 'Online Cookbook'
|
||||
@project1.save!
|
||||
project1_new_name = RedmineDmsf::Webdav::ProjectResource.create_project_name(@project1)
|
||||
project1_new_uri = Addressable::URI.escape(project1_new_name)
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{project1_new_uri}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{project1_new_uri}/</D:href>")
|
||||
assert response.body.include?("<D:displayname>#{project1_new_name}</D:displayname>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{project1_new_uri}/</d:href>")
|
||||
assert response.body.include?("<d:displayname>#{project1_new_name}</d:displayname>")
|
||||
end
|
||||
|
||||
def test_propfind_depth0_on_project1_for_admin_with_cache
|
||||
def test_propfind_depth0_on_project1_for_admin
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '0'})
|
||||
assert_response 207 # MultiStatus
|
||||
# Project
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</D:href>")
|
||||
# A new PROPSTATS entry should have been created for project1
|
||||
result = RedmineDmsf::Webdav::Cache.read("PROPSTATS/#{@project1.identifier}")
|
||||
assert result
|
||||
assert result.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</D:href>")
|
||||
# Replace the PROPSTATS cache entry and make sure that it is used
|
||||
RedmineDmsf::Webdav::Cache.write("PROPSTATS/#{@project1.identifier}", "Cached PROPSTATS/#{@project1.identifier}")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</d:href>")
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '0'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?("Cached PROPSTATS/#{@project1.identifier}")
|
||||
end
|
||||
|
||||
def test_propfind_depth1_on_project1_for_admin_with_cache
|
||||
def test_propfind_depth1_on_project1_for_admin
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
# Project
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</D:href>")
|
||||
result = RedmineDmsf::Webdav::Cache.read("PROPFIND/#{@project1.id}")
|
||||
assert result
|
||||
assert result.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</D:href>")
|
||||
result = RedmineDmsf::Webdav::Cache.read("PROPSTATS/#{@project1.identifier}")
|
||||
assert result
|
||||
assert result.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</D:href>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/</d:href>")
|
||||
# Folders
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@folder1.title}/</D:href>")
|
||||
result = RedmineDmsf::Webdav::Cache.read("PROPSTATS/#{@project1.identifier}/#{@folder1.title}")
|
||||
assert result
|
||||
assert result.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@folder1.title}/</D:href>")
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@folder6.title}/</D:href>")
|
||||
result = RedmineDmsf::Webdav::Cache.read("PROPSTATS/#{@project1.identifier}/#{@folder6.title}")
|
||||
assert result
|
||||
assert result.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@folder6.title}/</D:href>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@folder1.title}/</d:href>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@folder6.title}/</d:href>")
|
||||
# Files
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file1.name}</D:href>")
|
||||
result = RedmineDmsf::Webdav::Cache.read("PROPSTATS/#{@file1.id}-#{@file1.last_revision.id}")
|
||||
assert result
|
||||
assert result.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file1.name}</D:href>")
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file9.name}</D:href>")
|
||||
result = RedmineDmsf::Webdav::Cache.read("PROPSTATS/#{@file9.id}-#{@file9.last_revision.id}")
|
||||
assert result
|
||||
assert result.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file9.name}</D:href>")
|
||||
assert response.body.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file10.name}</D:href>")
|
||||
result = RedmineDmsf::Webdav::Cache.read("PROPSTATS/#{@file10.id}-#{@file10.last_revision.id}")
|
||||
assert result
|
||||
assert result.include?("<D:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file10.name}</D:href>")
|
||||
# Replace PROPFIND and verify that cached entry is used.
|
||||
RedmineDmsf::Webdav::Cache.write("PROPFIND/#{@project1.id}", "Cached PROPFIND/#{@project1.id}")
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?("Cached PROPFIND/#{@project1.id}")
|
||||
# Delete PROPFIND, replace PROPSTATS for one entry and verify that it is used when creating the response and new cache
|
||||
RedmineDmsf::Webdav::Cache.invalidate_item("PROPFIND/#{@project1.id}")
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}.invalid")
|
||||
RedmineDmsf::Webdav::Cache.write("PROPSTATS/#{@project1.identifier}", "Cached PROPSTATS/#{@project1.identifier}")
|
||||
# One PROPFIND entry is added and one .invalid entry is deleted, so no differenct.
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => "1"})
|
||||
assert_response 207 # MultiStatus
|
||||
assert response.body.include?("Cached PROPSTATS/#{@project1.identifier}")
|
||||
result = RedmineDmsf::Webdav::Cache.read("PROPFIND/#{@project1.id}")
|
||||
assert result
|
||||
assert result.include?("Cached PROPSTATS/#{@project1.identifier}")
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}.invalid")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file1.name}</d:href>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file9.name}</d:href>")
|
||||
assert response.body.include?("<d:href>http://www.example.com:80/dmsf/webdav/#{@project1.identifier}/#{@file10.name}</d:href>")
|
||||
end
|
||||
|
||||
def test_propfind_depth1_on_project1_for_admin_with_cache_and_locks
|
||||
def test_propfind_depth1_on_project1_for_admin
|
||||
log_user 'admin', 'admin' # login as admin
|
||||
assert !User.current.anonymous?, 'Current user is anonymous'
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
# Verify that everything exists in the cache as it should
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder1.title}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder6.title}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file1.id}-#{@file1.last_revision.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file9.id}-#{@file9.last_revision.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file10.id}-#{@file10.last_revision.id}")
|
||||
# Lock a file and verify that the PROPSTATS for the file and the PROPFIND were deleted
|
||||
assert @file1.lock!, "File failed to be locked by #{User.current.name}"
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}.invalid")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder1.title}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder6.title}")
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file1.id}-#{@file1.last_revision.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file9.id}-#{@file9.last_revision.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file10.id}-#{@file10.last_revision.id}")
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}.invalid")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file1.id}-#{@file1.last_revision.id}")
|
||||
# Unlock a file and verify that the PROPSTATS for the file and the PROPFIND were deleted
|
||||
assert @file1.unlock!, "File failed to be unlocked by #{User.current.name}"
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}.invalid")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder1.title}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder6.title}")
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file1.id}-#{@file1.last_revision.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file9.id}-#{@file9.last_revision.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file10.id}-#{@file10.last_revision.id}")
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}.invalid")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file1.id}-#{@file1.last_revision.id}")
|
||||
# Lock a folder and verify that the PROPSTATS for the file and the PROPFIND were deleted
|
||||
assert @folder1.lock!, "File failed to be locked by #{User.current.name}"
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}.invalid")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}")
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder1.title}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder6.title}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file1.id}-#{@file1.last_revision.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file9.id}-#{@file9.last_revision.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file10.id}-#{@file10.last_revision.id}")
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}.invalid")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder1.title}")
|
||||
# Unlock a folder and verify that the PROPSTATS for the file and the PROPFIND were deleted
|
||||
assert @folder1.unlock!, "File failed to be unlocked by #{User.current.name}"
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}.invalid")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}")
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder1.title}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder6.title}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file1.id}-#{@file1.last_revision.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file9.id}-#{@file9.last_revision.id}")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@file10.id}-#{@file10.last_revision.id}")
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}.invalid")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("PROPSTATS/#{@project1.identifier}/#{@folder1.title}")
|
||||
end
|
||||
|
||||
def test_webdav_caching_disabled
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] = '0'
|
||||
log_user 'admin', 'admin' # login as admin
|
||||
xml_http_request :propfind, "/dmsf/webdav/#{@project1.identifier}", nil, @admin.merge!({:HTTP_DEPTH => '1'})
|
||||
assert_response 207 # MultiStatus
|
||||
# Verify that everything exists in the cache as it should
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("PROPFIND/#{@project1.id}")
|
||||
end
|
||||
|
||||
end
|
||||
@ -125,35 +125,6 @@ class DmsfFileRevisionTest < RedmineDmsf::Test::UnitTest
|
||||
|
||||
assert_not_equal r1.disk_filename, r2.disk_filename, "The disk filename should not be equal for two revisions."
|
||||
end
|
||||
|
||||
def test_save_and_destroy_with_cache
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] = '1'
|
||||
Rails.cache.clear
|
||||
# save
|
||||
cache_key = @revision1.propfind_cache_key
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, "")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@revision1.save
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
RedmineDmsf::Webdav::Cache.delete("#{cache_key}.invalid")
|
||||
# destroy
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, "")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@revision1.destroy
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
# save!
|
||||
cache_key = @revision2.propfind_cache_key
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, "")
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@revision2.save!
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
end
|
||||
|
||||
def test_workflow_tooltip
|
||||
@revision2.set_workflow @wf1.id, 'start'
|
||||
|
||||
@ -165,43 +165,6 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest
|
||||
assert_equal new_file.dmsf_file_revisions.count, 1
|
||||
end
|
||||
end
|
||||
|
||||
def test_save_and_destroy_with_cache
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] = '1'
|
||||
Rails.cache.clear
|
||||
# save
|
||||
cache_key = @file5.propfind_cache_key
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, '')
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@file5.save
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
RedmineDmsf::Webdav::Cache.delete("#{cache_key}.invalid")
|
||||
# destroy
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, '')
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@file5.destroy
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
# save!
|
||||
cache_key = @file6.propfind_cache_key
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, '')
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@file6.save!
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
RedmineDmsf::Webdav::Cache.delete("#{cache_key}.invalid")
|
||||
# destroy!
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, '')
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@file6.destroy!
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
end
|
||||
|
||||
def test_project_project
|
||||
project = @file1.project
|
||||
|
||||
@ -142,44 +142,6 @@ class DmsfFolderTest < RedmineDmsf::Test::UnitTest
|
||||
"The expected position of the 'version_calculated' column is 14"
|
||||
end
|
||||
|
||||
def test_save_and_destroy_with_cache
|
||||
Setting.plugin_redmine_dmsf['dmsf_webdav_caching_enabled'] = '1'
|
||||
Rails.cache.clear
|
||||
# save
|
||||
cache_key = @folder4.propfind_cache_key
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, '')
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@folder4.save
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
RedmineDmsf::Webdav::Cache.delete("#{cache_key}.invalid")
|
||||
# destroy
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, '')
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@folder4.destroy
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
#RedmineDmsf::Webdav::Cache.cache.clear
|
||||
# save!
|
||||
cache_key = @folder5.propfind_cache_key
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, '')
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@folder5.save!
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
RedmineDmsf::Webdav::Cache.delete("#{cache_key}.invalid")
|
||||
# destroy!
|
||||
RedmineDmsf::Webdav::Cache.write(cache_key, '')
|
||||
assert RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
@folder5.destroy!
|
||||
assert !RedmineDmsf::Webdav::Cache.exist?(cache_key)
|
||||
assert RedmineDmsf::Webdav::Cache.exist?("#{cache_key}.invalid")
|
||||
end
|
||||
|
||||
def test_to_csv
|
||||
columns = %w(id title)
|
||||
csv = @folder4.to_csv(columns, 0)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user