From 2e431568d652ba942237e9d48a80dc2b77bbb2df Mon Sep 17 00:00:00 2001 From: Daniel Munn Date: Mon, 11 Jun 2012 11:30:04 +0100 Subject: [PATCH] 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. --- app/models/dmsf_file_revision.rb | 46 +++---- lib/redmine_dmsf/webdav/base_resource.rb | 22 +++- lib/redmine_dmsf/webdav/dmsf_resource.rb | 132 ++++++++++++++++++++ lib/redmine_dmsf/webdav/download.rb | 32 +++++ lib/redmine_dmsf/webdav/project_resource.rb | 15 +-- 5 files changed, 214 insertions(+), 33 deletions(-) create mode 100644 lib/redmine_dmsf/webdav/dmsf_resource.rb create mode 100644 lib/redmine_dmsf/webdav/download.rb diff --git a/app/models/dmsf_file_revision.rb b/app/models/dmsf_file_revision.rb index bbc63bb9..caee38b3 100644 --- a/app/models/dmsf_file_revision.rb +++ b/app/models/dmsf_file_revision.rb @@ -25,8 +25,8 @@ class DmsfFileRevision < ActiveRecord::Base belongs_to :deleted_by_user, :class_name => "User", :foreign_key => "deleted_by_user_id" 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}"}, :url => Proc.new {|o| {:controller => 'dmsf_files', :action => 'show', :id => o.file}}, :datetime => Proc.new {|o| o.updated_at }, @@ -79,9 +79,9 @@ class DmsfFileRevision < ActiveRecord::Base ["disk_filename = :filename", {:filename => self.disk_filename}]) 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} - CustomValue.find(:all, :conditions => "customized_id = " + self.id.to_s).each do |v| - v.destroy - end + CustomValue.find(:all, :conditions => "customized_id = " + self.id.to_s).each do |v| + v.destroy + end self.destroy else self.deleted = true @@ -134,8 +134,8 @@ class DmsfFileRevision < ActiveRecord::Base new_revision.name = self.name 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 end @@ -209,20 +209,20 @@ class DmsfFileRevision < ActiveRecord::Base 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 \ No newline at end of file + # 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 diff --git a/lib/redmine_dmsf/webdav/base_resource.rb b/lib/redmine_dmsf/webdav/base_resource.rb index 94a6748c..a8597664 100644 --- a/lib/redmine_dmsf/webdav/base_resource.rb +++ b/lib/redmine_dmsf/webdav/base_resource.rb @@ -1,6 +1,9 @@ module RedmineDmsf module Webdav class BaseResource < DAV4Rack::Resource + include Redmine::I18n + include ActionView::Helpers::NumberHelper + DIR_FILE = "%s%s%s%s" @@ -24,7 +27,15 @@ module RedmineDmsf @response.body = "" 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 ] end @@ -62,6 +73,12 @@ table { width:100%%; } end protected + def basename + File.basename(path) + end + def dirname + File.dirname(path) + end def Project return @Project unless @Project.nil? pinfo = @path.split('/').drop(1) @@ -72,6 +89,9 @@ table { width:100%%; } end end end + def projectless_path + '/'+path.split('/').drop(2).join('/') + end end end end diff --git a/lib/redmine_dmsf/webdav/dmsf_resource.rb b/lib/redmine_dmsf/webdav/dmsf_resource.rb new file mode 100644 index 00000000..1040293c --- /dev/null +++ b/lib/redmine_dmsf/webdav/dmsf_resource.rb @@ -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 + + diff --git a/lib/redmine_dmsf/webdav/download.rb b/lib/redmine_dmsf/webdav/download.rb new file mode 100644 index 00000000..0dcaec0b --- /dev/null +++ b/lib/redmine_dmsf/webdav/download.rb @@ -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 diff --git a/lib/redmine_dmsf/webdav/project_resource.rb b/lib/redmine_dmsf/webdav/project_resource.rb index 440fc514..6025a387 100644 --- a/lib/redmine_dmsf/webdav/project_resource.rb +++ b/lib/redmine_dmsf/webdav/project_resource.rb @@ -9,21 +9,18 @@ module RedmineDmsf def children #caching for repeat usage return @children unless @children.nil? + @children = [] DmsfFolder.project_root_folders(self.Project).map do |p| - child p.title, p + @children.push child(p.title, p) end DmsfFile.project_root_files(self.Project).map do |p| - child p.display_name, p + @children.push child(p.name, p) end - end - - - def name - self.Project.name unless self.Project.nil? + @children end def exist? - !self.Project.nil? + !(self.Project.nil? || User.current.anonymous?) end def collection? @@ -55,7 +52,7 @@ module RedmineDmsf end def special_type - "Project" + l(:field_project) end def content_length