#716 dav4rack -> planio/dav4rack

This commit is contained in:
Karel Picman 2018-01-30 15:56:35 +01:00
parent 28f9ab48ff
commit 3f1e6148ca
42 changed files with 405 additions and 1143 deletions

View File

@ -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*
------------------

View File

@ -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'

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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 = ''

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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$'

View File

@ -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'] }

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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)