Pass 1 of webdav (VERY ROUGH) - Functional on Get/Head (Web based listing) and also webdav listing and downloading of resources.

User based authentication and tracking of downloads, however security model not implemented yet.
This commit is contained in:
Daniel Munn 2012-06-11 11:30:04 +01:00
parent fa4207dfce
commit 2e431568d6
5 changed files with 214 additions and 33 deletions

View File

@ -25,8 +25,8 @@ class DmsfFileRevision < ActiveRecord::Base
belongs_to :deleted_by_user, :class_name => "User", :foreign_key => "deleted_by_user_id" belongs_to :deleted_by_user, :class_name => "User", :foreign_key => "deleted_by_user_id"
belongs_to :project belongs_to :project
acts_as_customizable acts_as_customizable
acts_as_event :title => Proc.new {|o| "#{l(:label_dmsf_updated)}: #{o.file.dmsf_path_str}"}, acts_as_event :title => Proc.new {|o| "#{l(:label_dmsf_updated)}: #{o.file.dmsf_path_str}"},
:url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o.file}}, :url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o.file}},
:datetime => Proc.new {|o| o.updated_at }, :datetime => Proc.new {|o| o.updated_at },
@ -79,9 +79,9 @@ class DmsfFileRevision < ActiveRecord::Base
["disk_filename = :filename", {:filename => self.disk_filename}]) ["disk_filename = :filename", {:filename => self.disk_filename}])
File.delete(self.disk_file) if dependent.length <= 1 && File.exist?(self.disk_file) File.delete(self.disk_file) if dependent.length <= 1 && File.exist?(self.disk_file)
DmsfFileRevisionAccess.find(:all, :conditions => ["dmsf_file_revision_id = ?", self.id]).each {|a| a.destroy} DmsfFileRevisionAccess.find(:all, :conditions => ["dmsf_file_revision_id = ?", self.id]).each {|a| a.destroy}
CustomValue.find(:all, :conditions => "customized_id = " + self.id.to_s).each do |v| CustomValue.find(:all, :conditions => "customized_id = " + self.id.to_s).each do |v|
v.destroy v.destroy
end end
self.destroy self.destroy
else else
self.deleted = true self.deleted = true
@ -134,8 +134,8 @@ class DmsfFileRevision < ActiveRecord::Base
new_revision.name = self.name new_revision.name = self.name
new_revision.folder = self.folder new_revision.folder = self.folder
new_revision.custom_values = self.custom_values.map(&:clone) new_revision.custom_values = self.custom_values.map(&:clone)
return new_revision return new_revision
end end
@ -209,20 +209,20 @@ class DmsfFileRevision < ActiveRecord::Base
end end
end end
end end
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
def available_custom_fields
search_project = nil
if self.project.present?
search_project = self.project
elsif self.project_id.present?
search_project = Project.find(self.project_id)
end
if search_project
search_project.all_dmsf_custom_fields
else
DmsfFileRevisionCustomField.all
end
end
end # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
def available_custom_fields
search_project = nil
if self.project.present?
search_project = self.project
elsif self.project_id.present?
search_project = Project.find(self.project_id)
end
if search_project
search_project.all_dmsf_custom_fields
else
DmsfFileRevisionCustomField.all
end
end
end

View File

@ -1,6 +1,9 @@
module RedmineDmsf module RedmineDmsf
module Webdav module Webdav
class BaseResource < DAV4Rack::Resource class BaseResource < DAV4Rack::Resource
include Redmine::I18n
include ActionView::Helpers::NumberHelper
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>" 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>"
@ -24,7 +27,15 @@ module RedmineDmsf
@response.body = "" @response.body = ""
Confict unless collection? Confict unless collection?
entities = children.map{|child| DIR_FILE % [child.public_path.html_safe, child.long_name || child.name, "-", child.special_type || child.content_type, child.last_modified]} * "\n" entities = children.map{|child|
DIR_FILE % [
child.public_path,
child.long_name || child.name,
child.collection? ? '-' : number_to_human_size(child.content_length),
child.special_type || child.content_type,
child.last_modified
]
} * "\n"
@response.body << index_page % [ path.empty? ? "/" : path, path.empty? ? "/" : path , entities ] @response.body << index_page % [ path.empty? ? "/" : path, path.empty? ? "/" : path , entities ]
end end
@ -62,6 +73,12 @@ table { width:100%%; }
end end
protected protected
def basename
File.basename(path)
end
def dirname
File.dirname(path)
end
def Project def Project
return @Project unless @Project.nil? return @Project unless @Project.nil?
pinfo = @path.split('/').drop(1) pinfo = @path.split('/').drop(1)
@ -72,6 +89,9 @@ table { width:100%%; }
end end
end end
end end
def projectless_path
'/'+path.split('/').drop(2).join('/')
end
end end
end end
end end

View File

@ -0,0 +1,132 @@
module RedmineDmsf
module Webdav
class DmsfResource < BaseResource
def children
NotFound unless exist? && folder?
return @children unless @children.nil?
@children = []
@_folderdata.subfolders.map do |p|
@children.push child(p.title, p)
end
@_folderdata.files.map do |p|
@children.push child(p.name, p)
end
@children
end
def exist?
folder? || file?
end
def collection?
exist? && folder?
end
def folder?
return @_folder unless @_folder.nil?
@_folder = false
folders = DmsfFolder.find(:all, :conditions => ["project_id = :project_id", {:project_id => self.Project.id}], :order => "title ASC")
folders.delete_if {|x| x.title != basename}
return false unless folders.length > 0
if (folders.length > 1) then
folders.delete_if {|x| x.dmsf_path_str != projectless_path}
return false unless folders.length > 0
@_folder=true
@_folderdata = folders[0]
else
if ('/'+folders[0].dmsf_path_str == projectless_path) then
@_folder=true
@_folderdata = folders[0]
else
@_folder= false
end
end
@_folder
end
def file?
return @_file unless @_file.nil?
@_file = false
files = DmsfFile.find(:all, :conditions => ["project_id = :project_id AND name = :file_name AND deleted = :deleted", {:project_id => self.Project.id, :file_name => basename, :deleted => false}], :order => "name ASC")
files.delete_if {|x| File.dirname('/'+x.dmsf_path_str) != File.dirname(projectless_path)}
if files.length > 0
@_filedata = files[0]
@_file = true
end
end
def content_type
if folder? then
"inode/directory"
elsif file?
@_filedata.last_revision.detect_content_type
else
NotFound
end
end
def creation_date
if folder?
@_folderdata.created_at
elsif file?
@_filedata.created_at
else
NotFound
end
end
def last_modified
if folder?
@_folderdata.updated_at
elsif file?
@_filedata.updated_at
else
NotFound
end
end
def etag
filesize = file? ? @_filedata.size : 4096;
fileino = file? ? File.stat(@_filedata.last_revision.disk_file).ino : 2;
sprintf('%x-%x-%x', fileino, filesize, last_modified.to_i)
end
def content_length
file? ? @_filedata.size : 4096;
end
def special_type
l(:field_folder) if folder?
end
def get(request, response)
raise NotFound unless exist?
if collection?
html_display
response['Content-Length'] = response.body.bytesize.to_s
else
response.body = download
end
OK
end
protected
def download
raise NotFound unless file?
#log_activity("downloaded")
if @request.env['HTTP_RANGE'].nil?
access = DmsfFileRevisionAccess.new(:user_id => User.current.id, :dmsf_file_revision_id => @_filedata.last_revision.id,
:action => DmsfFileRevisionAccess::DownloadAction)
access.save!
end
Download.new(@_filedata.last_revision.disk_file)
end
end
end
end

View File

@ -0,0 +1,32 @@
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
raise NotFound
end
end
end
end
end

View File

@ -9,21 +9,18 @@ module RedmineDmsf
def children def children
#caching for repeat usage #caching for repeat usage
return @children unless @children.nil? return @children unless @children.nil?
@children = []
DmsfFolder.project_root_folders(self.Project).map do |p| DmsfFolder.project_root_folders(self.Project).map do |p|
child p.title, p @children.push child(p.title, p)
end end
DmsfFile.project_root_files(self.Project).map do |p| DmsfFile.project_root_files(self.Project).map do |p|
child p.display_name, p @children.push child(p.name, p)
end end
end @children
def name
self.Project.name unless self.Project.nil?
end end
def exist? def exist?
!self.Project.nil? !(self.Project.nil? || User.current.anonymous?)
end end
def collection? def collection?
@ -55,7 +52,7 @@ module RedmineDmsf
end end
def special_type def special_type
"Project" l(:field_project)
end end
def content_length def content_length