From 75a443dac9b07939580f3ff7750d4ea722ebc157 Mon Sep 17 00:00:00 2001
From: "COLA@Redminetest"
Date: Wed, 23 Nov 2016 11:37:19 +0100
Subject: [PATCH 1/4] Implemented caching of PROPSTATS and PROPFIND using
MemCacheStore (memcached).
---
Gemfile | 1 +
app/models/dmsf_file.rb | 29 ++++++
app/models/dmsf_file_revision.rb | 17 +++-
app/models/dmsf_folder.rb | 33 ++++++-
app/views/settings/_dmsf_settings.html.erb | 7 ++
config/locales/en.yml | 6 +-
init.rb | 3 +-
lib/redmine_dmsf.rb | 5 ++
lib/redmine_dmsf/webdav/cache.rb | 83 ++++++++++++++++++
lib/redmine_dmsf/webdav/controller.rb | 97 ++++++++++++++++++---
lib/redmine_dmsf/webdav/dmsf_resource.rb | 4 +
lib/redmine_dmsf/webdav/project_resource.rb | 4 +
lib/redmine_dmsf/webdav/resource_proxy.rb | 4 +
13 files changed, 278 insertions(+), 15 deletions(-)
create mode 100644 lib/redmine_dmsf/webdav/cache.rb
diff --git a/Gemfile b/Gemfile
index 3a1540e0..59ed86e7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -27,6 +27,7 @@ gem 'zip-zip'
gem 'simple_enum'
gem 'uuidtools'
gem 'dav4rack'
+gem 'dalli'
group :xapian do
gem 'xapian-full-alaveteli', :require => false
diff --git a/app/models/dmsf_file.rb b/app/models/dmsf_file.rb
index 3ac5897a..a11810da 100644
--- a/app/models/dmsf_file.rb
+++ b/app/models/dmsf_file.rb
@@ -478,4 +478,33 @@ 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
+ if dmsf_folder_id.nil?
+ # File is in project root
+ return "PROPFIND/#{project_id}"
+ else
+ return "PROPFIND/#{project_id}/#{dmsf_folder_id}"
+ end
+ end
+
end
diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb
index 5f8f8958..84a002b3 100644
--- a/app/models/dmsf_file_revision.rb
+++ b/app/models/dmsf_file_revision.rb
@@ -109,6 +109,7 @@ class DmsfFileRevision < ActiveRecord::Base
dependencies = DmsfFileRevision.where(:disk_filename => self.disk_filename).all.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
@@ -290,5 +291,19 @@ 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
-end
\ No newline at end of file
+ def save!(*args)
+ RedmineDmsf::Webdav::Cache.invalidate_item(propfind_cache_key)
+ super(*args)
+ end
+
+ def propfind_cache_key
+ dmsf_file.propfind_cache_key
+ end
+
+end
diff --git a/app/models/dmsf_folder.rb b/app/models/dmsf_folder.rb
index de97c8ad..5bdfc1dd 100644
--- a/app/models/dmsf_folder.rb
+++ b/app/models/dmsf_folder.rb
@@ -295,7 +295,7 @@ class DmsfFolder < ActiveRecord::Base
file_links.visible.count +
url_links.visible.count
end
-
+
def self.is_column_on?(column)
columns = Setting.plugin_redmine_dmsf['dmsf_columns']
columns = DmsfFolder::DEFAULT_COLUMNS unless columns
@@ -395,6 +395,35 @@ 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
+
private
def self.directory_subtree(tree, folder, level, current_folder)
@@ -406,4 +435,4 @@ class DmsfFolder < ActiveRecord::Base
end
end
-end
\ No newline at end of file
+end
diff --git a/app/views/settings/_dmsf_settings.html.erb b/app/views/settings/_dmsf_settings.html.erb
index b810921a..da62bbb2 100644
--- a/app/views/settings/_dmsf_settings.html.erb
+++ b/app/views/settings/_dmsf_settings.html.erb
@@ -176,6 +176,13 @@
<%= l(:note_webdav_strategy).html_safe %> <%= l(:label_default) %>: <%= l(:select_option_webdav_readonly) %>
+
+ <%= content_tag(:label, l(:label_memcached_servers)) %>
+ <%= text_field_tag 'settings[dmsf_memcached_servers]', @settings['dmsf_memcached_servers'], :size => 50 %>
+
+ <%= l(:text_memcached_servers) %>
+
+
<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 12f5eac2..afa0d3ea 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -338,4 +338,8 @@ en:
label_webdav: WebDAV
label_full_text: Full-text search
- link_extension: Ext
\ No newline at end of file
+ link_extension: Ext
+
+ label_memcached_servers: "memcached server address"
+ text_memcached_servers: "Address to memcached server. Only a single server is supported, if empty then caching is disabled. After changing this the server must be restarted!"
+
diff --git a/init.rb b/init.rb
index a7a5090b..eb36c91c 100644
--- a/init.rb
+++ b/init.rb
@@ -47,7 +47,8 @@ Redmine::Plugin.register :redmine_dmsf do
'dmsf_webdav' => '1',
'dmsf_display_notified_recipients' => 0,
'dmsf_global_title_format' => '',
- 'dmsf_columns' => %w(title size modified version workflow author)
+ 'dmsf_columns' => %w(title size modified version workflow author),
+ 'dmsf_memcached_servers' => ''
}
menu :project_menu, :dmsf, { :controller => 'dmsf', :action => 'show' }, :caption => :menu_dmsf, :before => :documents, :param => :id
diff --git a/lib/redmine_dmsf.rb b/lib/redmine_dmsf.rb
index 9fe7693d..f3484dec 100644
--- a/lib/redmine_dmsf.rb
+++ b/lib/redmine_dmsf.rb
@@ -35,6 +35,7 @@ require 'redmine_dmsf/patches/user_patch'
# Load up classes that make up our WebDAV solution ontop of DAV4Rack
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'
@@ -57,3 +58,7 @@ require 'redmine_dmsf/hooks/views/my_account_view_hooks'
# Macros
require 'redmine_dmsf/macros'
+
+# Add the plugin view folder into ActionMailer's paths to search
+ActionMailer::Base.append_view_path(File.expand_path(
+ File.dirname(__FILE__) + '/../app/views'))
diff --git a/lib/redmine_dmsf/webdav/cache.rb b/lib/redmine_dmsf/webdav/cache.rb
new file mode 100644
index 00000000..f985c960
--- /dev/null
+++ b/lib/redmine_dmsf/webdav/cache.rb
@@ -0,0 +1,83 @@
+# encoding: utf-8
+#
+# Redmine plugin for Document Management System "Features"
+#
+# Copyright (C) 2012 Daniel Munn
+# Copyright (C) 2011-16 Karel Picman
+#
+# 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)
+ init unless defined?(@@WebDAVCache)
+ @@WebDAVCache.read(name, options)
+ end
+
+ def self.write(name, value, options = nil)
+ init unless defined?(@@WebDAVCache)
+ @@WebDAVCache.write(name, value, options)
+ end
+
+ def self.delete(name, options = nil)
+ init unless defined?(@@WebDAVCache)
+ @@WebDAVCache.delete(name, options)
+ end
+
+ def self.exist?(name, options = nil)
+ init unless defined?(@@WebDAVCache)
+ @@WebDAVCache.exist?(name, options)
+ end
+
+ def self.invalidate_item(key)
+ init unless defined?(@@WebDAVCache)
+ # Write an .invalid entry to notify anyone that is currently creating a response
+ # that that response is invalid and should not be cached
+ @@WebDAVCache.write("#{key}.invalid", expires_in: 60.seconds)
+ # Delete any existing entry in the cache
+ @@WebDAVCache.delete(key)
+ end
+
+ def self.cache
+ @@WebDAVCache
+ end
+
+ def self.init_testcache
+ puts "Webdav::Cache: Enable MemoryStore cache."
+ @@WebDAVCache = ActiveSupport::Cache::MemoryStore.new(options={:namespace => "RedmineDmsfWebDAV"})
+ end
+
+ def self.init_nullcache
+ puts "Webdav::Cache: Disable cache."
+ @@WebDAVCache = ActiveSupport::Cache::NullStore.new
+ end
+
+ private
+
+ def self.init
+ if Setting.plugin_redmine_dmsf['dmsf_memcached_servers'].nil? || Setting.plugin_redmine_dmsf['dmsf_memcached_servers'].empty?
+ # Disable caching by using a null cache
+ Rails.logger.info "Webdav::Cache: Cache disabled!"
+ @@WebDAVCache = ActiveSupport::Cache::NullStore.new
+ else
+ # Create cache using the provided server address
+ Rails.logger.info "Webdav::Cache: Cache enabled, using memcached server '#{Setting.plugin_redmine_dmsf['dmsf_memcached_servers']}'"
+ @@WebDAVCache = ActiveSupport::Cache::MemCacheStore.new(Setting.plugin_redmine_dmsf['dmsf_memcached_servers'], options={:namespace => "RedmineDmsfWebDAV"})
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/redmine_dmsf/webdav/controller.rb b/lib/redmine_dmsf/webdav/controller.rb
index a9a70e4e..dfbfaeea 100644
--- a/lib/redmine_dmsf/webdav/controller.rb
+++ b/lib/redmine_dmsf/webdav/controller.rb
@@ -107,18 +107,44 @@ module RedmineDmsf
raise BadRequest
end
end
- multistatus do |xml|
- find_resources.each do |resource|
- xml.response do
- unless(resource.propstat_relative_path)
- xml.href "#{scheme}://#{host}:#{port}#{url_format(resource)}"
- else
- xml.href url_format(resource)
- end
- propstats(xml, get_properties(resource, properties.empty? ? resource.properties : properties))
+
+ 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
+ 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
@@ -179,6 +205,57 @@ module RedmineDmsf
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}"
+ end
+
+ xml_str = RedmineDmsf::Webdav::Cache.read(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)
+ end
+ xml << xml_str
+ end
+ end
+ end
+
end
end
end
diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb
index d3fc6cd6..cc0b6440 100644
--- a/lib/redmine_dmsf/webdav/dmsf_resource.rb
+++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb
@@ -79,6 +79,10 @@ module RedmineDmsf
def really_exist?
return project && project.module_enabled?('dmsf') && (folder || file)
end
+
+ def project_id
+ project.id unless project.nil?
+ end
# Is this entity a folder?
def collection?
diff --git a/lib/redmine_dmsf/webdav/project_resource.rb b/lib/redmine_dmsf/webdav/project_resource.rb
index 932d7ca3..371f67dc 100644
--- a/lib/redmine_dmsf/webdav/project_resource.rb
+++ b/lib/redmine_dmsf/webdav/project_resource.rb
@@ -78,6 +78,10 @@ module RedmineDmsf
def long_name
project.name unless project.nil?
end
+
+ def project_id
+ project.id unless project.nil?
+ end
def content_type
'inode/directory'
diff --git a/lib/redmine_dmsf/webdav/resource_proxy.rb b/lib/redmine_dmsf/webdav/resource_proxy.rb
index 4eb0c2e4..484bb491 100644
--- a/lib/redmine_dmsf/webdav/resource_proxy.rb
+++ b/lib/redmine_dmsf/webdav/resource_proxy.rb
@@ -84,6 +84,10 @@ module RedmineDmsf
def really_exist?
@resource_c.really_exist?
end
+
+ def project_id
+ @resource_c.project_id
+ end
def creation_date
@resource_c.creation_date
From dd73aade584ef71204a87e815ee494fdf41d8181 Mon Sep 17 00:00:00 2001
From: "COLA@Redminetest"
Date: Sun, 15 Jan 2017 19:55:39 +0100
Subject: [PATCH 2/4] Added save and destroy with cache tests.
---
test/unit/dmsf_file_revision_test.rb | 41 ++++++++++++++++++++++--
test/unit/dmsf_file_test.rb | 44 +++++++++++++++++++++++++
test/unit/dmsf_folder_test.rb | 48 +++++++++++++++++++++++++++-
3 files changed, 130 insertions(+), 3 deletions(-)
diff --git a/test/unit/dmsf_file_revision_test.rb b/test/unit/dmsf_file_revision_test.rb
index 29281c14..dded0c48 100644
--- a/test/unit/dmsf_file_revision_test.rb
+++ b/test/unit/dmsf_file_revision_test.rb
@@ -25,11 +25,15 @@ class DmsfFileRevisionTest < RedmineDmsf::Test::UnitTest
:member_roles, :enabled_modules, :enumerations, :dmsf_locks
def setup
- @revision5 = DmsfFileRevision.find_by_id 5
+ @revision1 = DmsfFileRevision.find_by_id 1
+ @revision2 = DmsfFileRevision.find_by_id 2
+ @revision5 = DmsfFileRevision.find_by_id 5
end
def test_truth
- assert_kind_of DmsfFileRevision, @revision5
+ assert_kind_of DmsfFileRevision, @revision1
+ assert_kind_of DmsfFileRevision, @revision2
+ assert_kind_of DmsfFileRevision, @revision5
end
def test_delete_restore
@@ -49,5 +53,38 @@ class DmsfFileRevisionTest < RedmineDmsf::Test::UnitTest
def test_create_digest
assert_equal @revision5.create_digest, 0, "MD5 should be 0, if the file is missing"
end
+
+ def test_save_and_destroy_with_cache
+ RedmineDmsf::Webdav::Cache.init_testcache
+
+ # 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")
+
+ RedmineDmsf::Webdav::Cache.init_nullcache
+ end
end
\ No newline at end of file
diff --git a/test/unit/dmsf_file_test.rb b/test/unit/dmsf_file_test.rb
index 0e1c9299..ba600c16 100644
--- a/test/unit/dmsf_file_test.rb
+++ b/test/unit/dmsf_file_test.rb
@@ -35,6 +35,7 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest
@file3 = DmsfFile.find_by_id 3
@file4 = DmsfFile.find_by_id 4
@file5 = DmsfFile.find_by_id 5
+ @file6 = DmsfFile.find_by_id 6
User.current = nil
end
@@ -47,6 +48,7 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest
assert_kind_of DmsfFile, @file3
assert_kind_of DmsfFile, @file4
assert_kind_of DmsfFile, @file5
+ assert_kind_of DmsfFile, @file6
end
def test_project_file_count_differs_from_project_visibility_count
@@ -134,5 +136,47 @@ class DmsfFileTest < RedmineDmsf::Test::UnitTest
assert_equal 0, @file4.referenced_links.count
@file4.dmsf_folder.lock!
end
+
+ def test_save_and_destroy_with_cache
+ RedmineDmsf::Webdav::Cache.init_testcache
+
+ # 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")
+
+ RedmineDmsf::Webdav::Cache.init_nullcache
+ end
end
\ No newline at end of file
diff --git a/test/unit/dmsf_folder_test.rb b/test/unit/dmsf_folder_test.rb
index 12f80858..e45029ef 100644
--- a/test/unit/dmsf_folder_test.rb
+++ b/test/unit/dmsf_folder_test.rb
@@ -25,10 +25,14 @@ class DmsfFolderTest < RedmineDmsf::Test::UnitTest
fixtures :projects, :users, :email_addresses, :dmsf_folders, :roles, :members, :member_roles
def setup
+ @folder4 = DmsfFolder.find_by_id 4
+ @folder5 = DmsfFolder.find_by_id 5
@folder6 = DmsfFolder.find_by_id 6
end
def test_truth
+ assert_kind_of DmsfFolder, @folder4
+ assert_kind_of DmsfFolder, @folder5
assert_kind_of DmsfFolder, @folder6
end
@@ -96,5 +100,47 @@ class DmsfFolderTest < RedmineDmsf::Test::UnitTest
assert_equal DmsfFolder.get_column_position('version_calculated'), 11,
"The expected position of the 'version_calculated' column is 14"
end
-
+
+ def test_save_and_destroy_with_cache
+ RedmineDmsf::Webdav::Cache.init_testcache
+
+ # 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")
+
+ RedmineDmsf::Webdav::Cache.init_nullcache
+ end
end
\ No newline at end of file
From 5f57980bd82d088d1f5c0a22038570335e56a8b7 Mon Sep 17 00:00:00 2001
From: "COLA@Redminetest"
Date: Fri, 13 Jan 2017 22:24:18 +0100
Subject: [PATCH 3/4] Updated README
---
README.md | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/README.md b/README.md
index ff22bdfc..b9b5f933 100644
--- a/README.md
+++ b/README.md
@@ -251,6 +251,17 @@ Example of cron job (once per hour at 8th minute):
See redmine_dmsf/extra/xapian_indexer.rb for help.
+### WebDAV caching (optional, experimental!)
+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 , 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.
From e0f0ec4763b988c1cd53b565188345624272967a Mon Sep 17 00:00:00 2001
From: "COLA@Redminetest"
Date: Sun, 15 Jan 2017 21:41:30 +0100
Subject: [PATCH 4/4] Removed some stuff that was removed way back.
---
lib/redmine_dmsf.rb | 4 ----
1 file changed, 4 deletions(-)
diff --git a/lib/redmine_dmsf.rb b/lib/redmine_dmsf.rb
index f3484dec..6ad7bb79 100644
--- a/lib/redmine_dmsf.rb
+++ b/lib/redmine_dmsf.rb
@@ -58,7 +58,3 @@ require 'redmine_dmsf/hooks/views/my_account_view_hooks'
# Macros
require 'redmine_dmsf/macros'
-
-# Add the plugin view folder into ActionMailer's paths to search
-ActionMailer::Base.append_view_path(File.expand_path(
- File.dirname(__FILE__) + '/../app/views'))