Multiple fixes: upload not rendered for DMSF root; dmsf_lock is now storing uuid for lock; db upgrade / downgrade script updated for support

This commit is contained in:
Daniel Munn 2012-06-29 08:26:43 +01:00
parent 6237e09581
commit b8716a5cc2
6 changed files with 63 additions and 32 deletions

View File

@ -11,6 +11,7 @@ Changelog for Redmine DMSF
* Update: Webdav locks files for 1 hour at a time (requested time is ignored) * Update: Webdav locks files for 1 hour at a time (requested time is ignored)
* New: Files are now stored in project relevent folder * New: Files are now stored in project relevent folder
* New: Implementation of lockdiscovery and supportedlock property requests * New: Implementation of lockdiscovery and supportedlock property requests
* New: Locks store a timestamp based UUID string enabling better interaction with webservices
1.4.3: *2012-06-26* 1.4.3: *2012-06-26*
----------------- -----------------

View File

@ -17,8 +17,8 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class DmsfLock < ActiveRecord::Base class DmsfLock < ActiveRecord::Base
unloadable # unloadable
before_create :generate_uuid
belongs_to :file, :class_name => "DmsfFile", :foreign_key => "entity_id" belongs_to :file, :class_name => "DmsfFile", :foreign_key => "entity_id"
belongs_to :folder, :class_name => "DmsfFolder", :foreign_key => "entity_id" belongs_to :folder, :class_name => "DmsfFolder", :foreign_key => "entity_id"
belongs_to :user belongs_to :user
@ -46,8 +46,28 @@ class DmsfLock < ActiveRecord::Base
return expires_at <= Time.now return expires_at <= Time.now
end end
def generate_uuid
self.uuid = UUIDTools::UUID.timestamp_create().to_s
end
def self.delete_expired def self.delete_expired
self.delete_all ['expires_at IS NOT NULL && expires_at < ?', Time.now] self.delete_all ['expires_at IS NOT NULL && expires_at < ?', Time.now]
end end
#Lets allow our UUID to be searchable
def self.find(*args)
if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
lock = find_by_uuid(*args)
raise ActiveRecord::RecordNotFound, "Couldn't find lock with uuid=#{args.first}" if lock.nil?
lock
else
super
end
end
def self.find_by_param(*args)
self.find(*args)
end
end end

View File

@ -315,5 +315,6 @@ sUrl = "jquery.dataTables/#{I18n.locale.to_s.downcase}.json" if I18n.locale && !
</script> </script>
<% end %> <% end %>
<%= render(:partial => "multi_upload") if (User.current.allowed_to?(:file_manipulation, @project) && (!@folder.nil? && !@folder.locked_for_user?)) %> <%= render(:partial => "multi_upload") if (User.current.allowed_to?(:file_manipulation, @project) &&
( @folder.nil? || (!@folder.nil? &&!@folder.locked_for_user?) ) ) %>
<br /> <br />

View File

@ -17,6 +17,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'fileutils' require 'fileutils'
require 'uuidtools'
class Dmsf144 < ActiveRecord::Migration class Dmsf144 < ActiveRecord::Migration
@ -34,6 +35,7 @@ class Dmsf144 < ActiveRecord::Migration
#Add our lock relevent columns (ENUM) - null (till we upgrade data) #Add our lock relevent columns (ENUM) - null (till we upgrade data)
add_column :dmsf_file_locks, :lock_type_cd, :integer, :null => true add_column :dmsf_file_locks, :lock_type_cd, :integer, :null => true
add_column :dmsf_file_locks, :lock_scope_cd, :integer, :null => true add_column :dmsf_file_locks, :lock_scope_cd, :integer, :null => true
add_column :dmsf_file_locks, :uuid, :string, :null => true, :limit => 36
#Add our expires_at column #Add our expires_at column
add_column :dmsf_file_locks, :expires_at, :datetime, :null => true add_column :dmsf_file_locks, :expires_at, :datetime, :null => true
@ -45,10 +47,20 @@ class Dmsf144 < ActiveRecord::Migration
end end
end end
#Generate new lock Id's for whats being persisted
do_not_delete.each {|l|
#Find the lock
next unless lock = DmsfFileLock.find(l)
lock.uuid = UUIDTools::UUID.timestamp_create().to_s
lock.save!
}
say "Preserving #{do_not_delete.count} file lock(s) found in old schema" say "Preserving #{do_not_delete.count} file lock(s) found in old schema"
DmsfFileLock.delete_all(['id NOT IN (?)', do_not_delete]) DmsfFileLock.delete_all(['id NOT IN (?)', do_not_delete])
#We need to force our newly found
say "Applying default lock scope / type - Exclusive / Write" say "Applying default lock scope / type - Exclusive / Write"
DmsfFileLock.update_all ['entity_type = ?, lock_type_cd = ?, lock_scope_cd = ?', 0, 0, 0] DmsfFileLock.update_all ['entity_type = ?, lock_type_cd = ?, lock_scope_cd = ?', 0, 0, 0]
@ -101,6 +113,7 @@ class Dmsf144 < ActiveRecord::Migration
remove_column :dmsf_file_locks, :lock_type_cd remove_column :dmsf_file_locks, :lock_type_cd
remove_column :dmsf_file_locks, :lock_scope_cd remove_column :dmsf_file_locks, :lock_scope_cd
remove_column :dmsf_file_locks, :expires_at remove_column :dmsf_file_locks, :expires_at
remove_column :dmsf_file_locks, :uuid
#Not sure if this is the right place to do this, as its file manipulation, not database (stricly) #Not sure if this is the right place to do this, as its file manipulation, not database (stricly)
begin begin

View File

@ -26,23 +26,18 @@ module RedmineDmsf
existing = locks(false) existing = locks(false)
raise DmsfLockError.new("Unable to complete lock - resource (or parent) is locked") if self.locked? && existing.empty? raise DmsfLockError.new("Unable to complete lock - resource (or parent) is locked") if self.locked? && existing.empty?
unless existing.empty? unless existing.empty?
if existing[0].lock_scope == :scope_exclusive if existing[0].lock_scope == :scope_shared && scope == :scope_shared
# If it's an exclusive lock and you're re-requesting the actual desired behaviour is to not return a new lock, # RFC states if an item is exclusively locked and another lock is attempted we reject
# but the same lock (extended) # if the item is shared locked however, we can always add another lock to it
if self.folder.locked? if self.folder.locked?
raise DmsfLockError.new("Unable to complete lock - resource parent is locked") raise DmsfLockError.new("Unable to complete lock - resource parent is locked")
else else
if existing[0].user.id == User.current.id then existing.each {|l|
l = existing[0] raise DmsfLockError.new("Unable to complete lock - resource is locked") if l.user.id == User.current.id
l.expires_at = expire }
l.save!
return l
else
raise DmsfLockError.new("Unable to complete lock - resource is locked")
end
end end
else else
raise DmsfLockError.new("unable to exclusively lock a shared-locked resource") if scope == :scope_exclusive raise DmsfLockError.new("unable to lock exclusively an already-locked resource") if scope == :scope_exclusive
end end
end end
l = DmsfLock.new l = DmsfLock.new

View File

@ -398,7 +398,6 @@ module RedmineDmsf
# Lock # Lock
def lock(args) def lock(args)
return Conflict unless (parent.projectless_path == "/" || parent_exists?) return Conflict unless (parent.projectless_path == "/" || parent_exists?)
token = UUIDTools::UUID.md5_create(UUIDTools::UUID_URL_NAMESPACE, projectless_path).to_s
lock_check(args[:scope]) lock_check(args[:scope])
entity = file? ? file : folder entity = file? ? file : folder
begin begin
@ -416,17 +415,12 @@ module RedmineDmsf
return Conflict if http_if.nil? return Conflict if http_if.nil?
http_if = http_if.slice(1, http_if.length - 2) http_if = http_if.slice(1, http_if.length - 2)
l = DmsfLock.find(http_if)
return Conflict unless http_if == token return Conflict if l.nil?
l.expires_at = Time.now + 1.hour
entity.lock(false).each {|l| l.save!
if l.user.id == User.current.id @response['Lock-Token'] = l.uuid
l.expires_at = Time.now + 1.hour return [1.hours.to_i, l.uuid]
l.save!
@response['Lock-Token'] = token
return [1.hours.to_i, token]
end
}
#Unfortunately if we're here, then it's updating a lock we can't find #Unfortunately if we're here, then it's updating a lock we can't find
@ -436,9 +430,10 @@ module RedmineDmsf
scope = "scope_#{(args[:scope] || "exclusive")}".to_sym scope = "scope_#{(args[:scope] || "exclusive")}".to_sym
type = "type_#{(args[:type] || "write")}".to_sym type = "type_#{(args[:type] || "write")}".to_sym
entity.lock! scope, type, Time.now + 1.hours #l should be the instance of the lock we've just created
@response['Lock-Token'] = token l = entity.lock!(scope, type, Time.now + 1.hours)
[1.hours.to_i, token] @response['Lock-Token'] = l.uuid
[1.hours.to_i, l.uuid]
end end
rescue DmsfLockError rescue DmsfLockError
raise DAV4Rack::LockFailure.new("Failed to lock: #{@path}") raise DAV4Rack::LockFailure.new("Failed to lock: #{@path}")
@ -455,9 +450,12 @@ module RedmineDmsf
BadRequest BadRequest
else else
begin begin
_token = UUIDTools::UUID.md5_create(UUIDTools::UUID_URL_NAMESPACE, projectless_path).to_s
entity = file? ? file : folder entity = file? ? file : folder
if (!entity.locked? || entity.locked_for_user? || token != _token) l = DmsfLock.find(token)
l_entity = l.file || l.folder
# Additional case: if a user trys to unlock the file instead of the folder that's locked
# This should throw forbidden as only the lock at level initiated should be unlocked
if (!entity.locked? || entity.locked_for_user? || l_entity != entity)
Forbidden Forbidden
else else
entity.unlock! entity.unlock!
@ -607,6 +605,9 @@ module RedmineDmsf
lock_path << lock_entity.dmsf_path.map {|x| URI.escape(x.respond_to?('name') ? x.name : x.title) }.join('/') lock_path << lock_entity.dmsf_path.map {|x| URI.escape(x.respond_to?('name') ? x.name : x.title) }.join('/')
lock_path << "/" if lock_entity.is_a?(DmsfFolder) && lock_path[-1,1] != '/' lock_path << "/" if lock_entity.is_a?(DmsfFolder) && lock_path[-1,1] != '/'
doc.lockroot { doc.href lock_path } doc.lockroot { doc.href lock_path }
if (lock.user.id == User.current.id || User.current.allowed_to?(:force_file_unlock, self.project))
doc.locktoken { doc.href lock.uuid }
end
} }
} }
} }