From c5d279e266b0985c980d9dd21c01ecfd4e55baa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Pi=C4=8Dman?= Date: Wed, 8 Apr 2015 10:04:46 +0200 Subject: [PATCH] Control DMSF via REST API #78 --- app/controllers/dmsf_controller.rb | 23 +++--- app/controllers/dmsf_upload_controller.rb | 81 +++++++++++++++++----- app/models/dmsf_upload.rb | 8 +-- app/views/dmsf/show.api.rsb | 30 ++++++++ app/views/dmsf_files/show.api.rsb | 6 +- app/views/dmsf_upload/commit.api.rsb | 3 + app/views/dmsf_upload/upload.api.rsb | 3 + config/routes.rb | 2 + extra/{ => api}/api_client.rb | 19 +++-- extra/api/api_client.sh | 42 +++++++++++ extra/api/cat.gif | Bin 0 -> 9982 bytes extra/api/file.xml | 12 ++++ extra/api/folder.xml | 6 ++ init.rb | 4 +- lib/redmine_dmsf/webdav/base_resource.rb | 2 +- lib/tasks/dmsf_convert_documents.rake | 13 +--- 16 files changed, 198 insertions(+), 56 deletions(-) create mode 100644 app/views/dmsf/show.api.rsb create mode 100644 app/views/dmsf_upload/commit.api.rsb create mode 100644 app/views/dmsf_upload/upload.api.rsb rename extra/{ => api}/api_client.rb (79%) create mode 100644 extra/api/api_client.sh create mode 100644 extra/api/cat.gif create mode 100644 extra/api/file.xml create mode 100644 extra/api/folder.xml diff --git a/app/controllers/dmsf_controller.rb b/app/controllers/dmsf_controller.rb index 65e355fe..59a45a57 100644 --- a/app/controllers/dmsf_controller.rb +++ b/app/controllers/dmsf_controller.rb @@ -31,6 +31,8 @@ class DmsfController < ApplicationController before_filter :authorize before_filter :find_folder, :except => [:new, :create, :edit_root, :save_root] before_filter :find_parent, :only => [:new, :create] + + accept_api_auth :show, :create helper :all @@ -134,7 +136,14 @@ class DmsfController < ApplicationController @trash_enabled = DmsfFolder.deleted.where(:project_id => @project.id).any? || DmsfFile.deleted.where(:project_id => @project.id).any? || DmsfLink.deleted.where(:project_id => @project.id).any? - end + + respond_to do |format| + format.html { + render :layout => !request.xhr? + } + format.api + end + end def trash @folder_manipulation_allowed = User.current.allowed_to? :folder_manipulation, @project @@ -249,13 +258,11 @@ class DmsfController < ApplicationController render :action => 'edit' end - def create - if (Rails::VERSION::MAJOR > 3) - @folder = DmsfFolder.new( - params.require(:dmsf_folder).permit(:title, :description, :dmsf_folder_id)) - else - @folder = DmsfFolder.new(params[:dmsf_folder]) - end + def create + @folder = DmsfFolder.new + @folder.title = params[:dmsf_folder][:title] + @folder.description = params[:dmsf_folder][:description] + @folder.dmsf_folder_id = params[:dmsf_folder][:dmsf_folder_id] @folder.project = @project @folder.user = User.current diff --git a/app/controllers/dmsf_upload_controller.rb b/app/controllers/dmsf_upload_controller.rb index 0c531d1e..1371bbf7 100644 --- a/app/controllers/dmsf_upload_controller.rb +++ b/app/controllers/dmsf_upload_controller.rb @@ -26,10 +26,12 @@ class DmsfUploadController < ApplicationController before_filter :find_project before_filter :authorize - before_filter :find_folder, :except => [:upload_file] + before_filter :find_folder, :except => [:upload_file, :upload] helper :all helper :dmsf_workflows + + accept_api_auth :upload, :commit def upload_files uploaded_files = params[:attachments] @@ -62,11 +64,7 @@ class DmsfUploadController < ApplicationController return end @disk_filename = DmsfHelper.temp_filename(@tempfile.original_filename) - File.open("#{DmsfHelper.temp_dir}/#{@disk_filename}", 'wb') do |f| - while (buffer = @tempfile.read(8192)) - f.write(buffer) - end - end + FileUtils.mv(@tempfile.path, "#{DmsfHelper.temp_dir}/#{@disk_filename}") if File.size("#{DmsfHelper.temp_dir}/#{@disk_filename}") <= 0 begin File.delete("#{DmsfHelper.temp_dir}/#{@disk_filename}") @@ -85,12 +83,55 @@ class DmsfUploadController < ApplicationController end end + # REST API document upload + def upload + unless request.content_type == 'application/octet-stream' + render :nothing => true, :status => 406 + return + end + + @attachment = Attachment.new(:file => request.raw_post) + @attachment.author = User.current + @attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16) + saved = @attachment.save + + respond_to do |format| + format.js + format.api { + if saved + render :action => 'upload', :status => :created + else + render_validation_errors(@attachment) + end + } + end + end + def commit_files if (Rails::VERSION::MAJOR > 3) commited_files = params.require(:commited_files) else commited_files = params[:commited_files] end + commit_files_internal commited_files + end + + # REST API file commit + def commit + uploaded_files = params[:attachments] + if uploaded_files && uploaded_files.is_a?(Hash) + # standard file input uploads + uploaded_files.each_value do |uploaded_file| + upload = DmsfUpload.create_from_uploaded_attachment(@project, @folder, uploaded_file) + uploaded_file[:disk_filename] = upload.disk_filename + end + end + commit_files_internal uploaded_files + end + + private + + def commit_files_internal(commited_files) if commited_files && commited_files.is_a?(Hash) files = [] failed_uploads = [] @@ -146,12 +187,12 @@ class DmsfUploadController < ApplicationController new_revision.mime_type = Redmine::MimeType.of(new_revision.name) new_revision.size = File.size(commited_disk_filepath) - file_upload = File.new(commited_disk_filepath, 'rb') - unless file_upload - failed_uploads.push(commited_file) - flash[:error] = l(:error_file_commit_require_uploaded_file) - next - end +# file_upload = File.new(commited_disk_filepath, 'rb') +# unless file_upload +# failed_uploads.push(commited_file) +# flash[:error] = l(:error_file_commit_require_uploaded_file) +# next +# end if file.locked? begin @@ -180,9 +221,10 @@ class DmsfUploadController < ApplicationController if new_revision.save new_revision.assign_workflow(commited_file[:dmsf_workflow_id]) - new_revision.copy_file_content(file_upload) - file_upload.close - File.delete(commited_disk_filepath) + #new_revision.copy_file_content(file_upload) + #file_upload.close + #File.delete(commited_disk_filepath) + FileUtils.mv(commited_disk_filepath, new_revision.disk_file) file.set_last_revision new_revision files.push(file) else @@ -213,10 +255,13 @@ class DmsfUploadController < ApplicationController flash[:warning] = l(:warning_some_files_were_not_commited, :files => failed_uploads.map{|u| u['name']}.join(', ')) end end - redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) + respond_to do |format| + format.js + format.api { render_validation_errors(failed_uploads) } + format.html { redirect_to dmsf_folder_path(:id => @project, :folder_id => @folder) } + end + end - - private def log_activity(file, action) Rails.logger.info "#{Time.now.strftime('%Y-%m-%d %H:%M:%S')} #{User.current.login}@#{request.remote_ip}/#{request.env['HTTP_X_FORWARDED_FOR']}: #{action} dmsf://#{file.project.identifier}/#{file.id}/#{file.last_revision.id}" diff --git a/app/models/dmsf_upload.rb b/app/models/dmsf_upload.rb index 41fc5145..31ae5566 100644 --- a/app/models/dmsf_upload.rb +++ b/app/models/dmsf_upload.rb @@ -45,13 +45,7 @@ class DmsfUpload :original_filename => a.filename, :comment => uploaded_file[:description] } - File.open(a.diskfile, 'rb') do |fr| - File.open("#{DmsfHelper.temp_dir}/#{uploaded[:disk_filename]}", 'wb') do |fw| - while (buffer = fr.read(8192)) - fw.write(buffer) - end - end - end + FileUtils.mv(a.diskfile, "#{DmsfHelper.temp_dir}/#{uploaded[:disk_filename]}") a.destroy DmsfUpload.new(project, folder, uploaded) end diff --git a/app/views/dmsf/show.api.rsb b/app/views/dmsf/show.api.rsb new file mode 100644 index 00000000..a1d33e73 --- /dev/null +++ b/app/views/dmsf/show.api.rsb @@ -0,0 +1,30 @@ +api.array :dmsf_folders, api_meta(:total_count => @subfolders.size) do + @subfolders.each do |folder| + api.folder do + api.id folder.id + api.title folder.title + end + end +end + +api.array :dmsf_files, api_meta(:total_count => @files.size) do + @files.each do |file| + api.file do + api.id file.id + api.name file.name + end + end +end + +api.array :dmsf_links, api_meta(:total_count => @dir_links.size + @file_links.size + @url_links.size) do + (@dir_links + @file_links + @url_links).each do |link| + api.link do + api.id link.id + api.name link.name + api.target_type link.target_type + api.target_id link.target_id + api.target_project_id link.target_project_id + api.external_url link.external_url if link.external_url.present? + end + end +end \ No newline at end of file diff --git a/app/views/dmsf_files/show.api.rsb b/app/views/dmsf_files/show.api.rsb index 8a66d0f3..4f265db2 100644 --- a/app/views/dmsf_files/show.api.rsb +++ b/app/views/dmsf_files/show.api.rsb @@ -1,8 +1,8 @@ api.file do api.id @file.id - api.name @file.name + api.name @file.name api.project_id @file.project_id - api.dmsf_folder_id @file.folder ? @file.dmsf_folder_id : -1 - api.version "#{@file.last_revision.major_version}.#{@file.last_revision.minor_version}" + api.dmsf_folder_id @file.folder if @file.dmsf_folder_id + api.version "#{@file.last_revision.major_version}.#{@file.last_revision.minor_version}" if @file.last_revision api.content_url url_for(:controller => :dmsf_files, :action => 'show', :download => '', :id => @file, :only_path => false) end \ No newline at end of file diff --git a/app/views/dmsf_upload/commit.api.rsb b/app/views/dmsf_upload/commit.api.rsb new file mode 100644 index 00000000..f1481c7d --- /dev/null +++ b/app/views/dmsf_upload/commit.api.rsb @@ -0,0 +1,3 @@ +api.commit do + puts 'OK' +end diff --git a/app/views/dmsf_upload/upload.api.rsb b/app/views/dmsf_upload/upload.api.rsb new file mode 100644 index 00000000..0a399ec1 --- /dev/null +++ b/app/views/dmsf_upload/upload.api.rsb @@ -0,0 +1,3 @@ +api.upload do + api.token @attachment.token +end diff --git a/config/routes.rb b/config/routes.rb index 0852c9ab..9569f325 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -58,7 +58,9 @@ RedmineApp::Application.routes.draw do post '/projects/:id/dmsf/upload/files', :controller => 'dmsf_upload', :action => 'upload_files' post '/projects/:id/dmsf/upload/file', :controller => 'dmsf_upload', :action => 'upload_file' + post '/projects/:id/dmsf/upload', :controller => 'dmsf_upload', :action => 'upload' post '/projects/:id/dmsf/upload/commit', :controller => 'dmsf_upload', :action => 'commit_files' + post '/projects/:id/dmsf/commit', :controller => 'dmsf_upload', :action => 'commit' # # dmsf_files controller diff --git a/extra/api_client.rb b/extra/api/api_client.rb similarity index 79% rename from extra/api_client.rb rename to extra/api/api_client.rb index 98de7da1..cb9554b9 100644 --- a/extra/api_client.rb +++ b/extra/api/api_client.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 +# # Redmine plugin for Document Management System "Features" # # Copyright (C) 2011-15 Karel Pičman @@ -19,22 +21,25 @@ require 'rubygems' require 'active_resource' +# Simple REST API client in Ruby +# usage: ruby api_client.rb [login] [password] + # Dmsf file class DmsfFile < ActiveResource::Base self.site = 'http://localhost:3000/' - self.user = 'kpicman' - self.password = 'Kontron2014+' + self.user = ARGV[0] + self.password = ARGV[1] end -# Retrieving a file -file = DmsfFile.find 17200 +# 2. Get a document +FILE_ID = 17216 +file = DmsfFile.find FILE_ID if file puts file.id puts file.name puts file.version - puts file.project_id - puts file.dmsf_folder_id + puts file.project_id puts file.content_url else - puts 'No file with id = 1 found' + puts "No file with id = #{FILE_ID} found" end \ No newline at end of file diff --git a/extra/api/api_client.sh b/extra/api/api_client.sh new file mode 100644 index 00000000..43e5f79b --- /dev/null +++ b/extra/api/api_client.sh @@ -0,0 +1,42 @@ +# encoding: utf-8 +# +# Redmine plugin for Document Management System "Features" +# +# Copyright (C) 2011-15 Karel Pičman +# +# 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. + +#!/bin/bash + +# Authentication as input parameters either as login + password or the API key +# -u ${1}:${2} +# -H "X-Redmine-API-Key: ${1}" + +# BOTH XML and JSON formats are supported. +# Just replace .xml with .json + +# 1. List of documents in a given folder or the root folder +curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000//projects/12/dmsf.xml +#curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000//projects/12/dmsf.xml?folder_id=5155 + +# 2. Get a document +#curl -v -H "Content-Type: application/xml" -X GET -u ${1}:${2} http://localhost:3000/dmsf/files/17216.xml + +# 3. Create a folder +#curl -v -H "Content-Type: application/xml" -X POST --data "@folder.xml" -u ${1}:${2} http://localhost:3000//projects/12/dmsf/create.xml + +# 4. Upload a document into a given folder or the root folder +#curl --data-binary "@cat.gif" -H "Content-Type: application/octet-stream" -X POST -u ${1}:${2} http://localhost:3000//projects/12/dmsf/upload.xml?filename=cat.gif +#curl -v -H "Content-Type: application/xml" -X POST --data "@file.xml" -u ${1}:${2} http://localhost:3000//projects/12/dmsf/commit.xml \ No newline at end of file diff --git a/extra/api/cat.gif b/extra/api/cat.gif new file mode 100644 index 0000000000000000000000000000000000000000..0329e659baf657fe9b0564f0d61fd97d58fbf8f5 GIT binary patch literal 9982 zcmbt(cU;ol8#ZM$v~rRyM`~tLsnPBM%#o|Yi8ykLqhUkKL@+gXgJ z79^Moqw--SJ`Br;!T2x`V2KZ+@N1&k$uup|(S1OnUx;sL+}W&_~>{0Be)9s;NVV*qFXB`_4g z0W1vI222CCK!V5#x`8(EgJH2CK_CJDfMtOe42Fe)fVcwg0w{n^EC_}L0dWK(4Fmwd z1q=rQ4R{NL5{L%?6POKz1Hc4040s5j28;oqftdg-00*!zU>h(E5>O-<4Rix-;0FR2 z0|E*72P_M;U?9QAA&Bcb--6C{3I$bv+yc64pHwSXu`y;A9<# z!0@_h;Bx?20)hwlGz|jLL7?{_P#y>r4g%c=fh<5E9S}$X1QG{e{C{*$Pfye7^!M-I z=jG*vhlk(4f8WBwLPtkOK|w)WTwwbEU*w9extaC_W8LG&)P*(({#ujK^T2BPcwBdJ zy(6!F?6|zs19_`^u6N~)J=}a;y+H>!n?PbfG>!m+0i=gPdRuv*4O{o?7o~p+-YThT z+gS8MJ8XH8v!Yj=Q6I&f&i8FB&iv>Xcw}D47$sj#kT%P+Z7Oj^5Y@Moyc-j>|KtZd z%q_h`ORIuNs@;5Sa3Ti|y}%#GZ7mr@r08ZErW%&#b-q45bMQt}!eq53XEEe!lA2fz z;h@~lmI^OuJ%JN?OsA*qMt9`e>fBI+uxm~Ax#Ht>(&5K+30&0@Q;b@%)2C=fqqIv_ zo>e&XB8lEI1MO%=&wxvC-5GuUY)xocL*XE!t;l()RH8F7;f?P?_R(;)H(mv6Oyv_( zg$%QS9Sg;_=Zfx6X>L<{iLT0E)#HUG54v6{N@GQ;4OrBcRiF;u3w^>M>=K1JrL%T+ z|Lpr(UCmorUYKNfmD?fOq`!WV(|ZC2I~Y;MPRV;Qcy}GH`;9y6!WVbMiy|AZ`p~Ru zd@430Xcq{X##}i)-#KhB$j} zYa(1r)*EHC`Xh(r#dCj@g;M?r+Y6nQDGP$^xyQ792>Ir2jm!$ENKf{qYey#^kzgk4 z{^=iIF=QUerB)v zSDe>|f!P0Y&ZGPYZY)-O=(ns|(zf+ubA6l&z_~|#rcp2pJum4#<0OxR+D$B~h?6hi z&R9}jZAl+N1}L7MwrwuUZGUN;sY;P{&+bg%Ef>$9o?~7LXHho&lzv4}Rl(}U@0Yii_o3gabWXTp zh~(<{5LJ#O}h0*g>lOWR_%bo{ns8{ ziWq*c6I#}PbAClE4l!pz#xIATiH09naG~EkK`4{F4*ea63f&D)veT+d?HJVCb^DBx zwx(THujW^iEM{zZetfsy9$ABfTGgCd8bSiHMN^+&RPL&!1ut{BylJvJ!7N1NrXq#bnspMJO0ruLR{l9gm3C)T6L#}%$8XcHHws`) z{KiEbw{MKm*Dcs>Xztbqkhs2aEgmf&k)Tfy6A)17R6;Gsw{F0f-ctUHZ=>=LZJR@lWy7SHTB6bxddyJoPGT2 zlmYdUUv|yoT~@^w0Ik;urvpZC*Jt~N+l~p{Kv0YNMk@n-jmig257`HlhS-#cdC9#V zr7T-aMbZlAC;yO$8}M7In2ji`?p)UZ>{;Z2Zd7eeA+8)p6;a>HTBs;x(Qx!D3PY}!Y92ZFAk&ducZck~?_u=mq zz6%rdmWo3A)PQXTjW6HGA@O!I`LE7=kJN#3bMh7|y3<_BlWhvJK9t!Zp33BJ|M2{5 zehuDW{N~7##sEBTO`oikXKLT|amx(8`mNRgJm8~8>1W>Tou2n~aZvkjAKosEwq_dX zdCstEdMjtZT)p$IHJa#(>T|Y^*(*(&0h77MzNTf-svh;Wpdz@xr#abQ`OZF-6=svN z8y=FaIm=;5kaS)<>e|MGXZj5(>dR*GJa1_mB;?94N`CXMGJ3EI%Q?ix(85huer2{+ z7`n14Nc7GYt531v%9}HY2sG1K*OJZ5WOyUAwklp&{-u(L4Ng% z$e&8pWY3nO?u*;yb_nMcn>xUu(PIboS-;{kpPZgh93b8L0x-GpubC9v*Hv3VBi97% z)?l|t&ka=J_h?7%Hz>(!(p=j4v$Wz?h(W+Adi#g4nT)2CQ!wsig_>MT5Q}14)>b_B zQS-oQYmUdCgH^C6JwHR1XNx~q!rU5Dd6|Ss?*jhx z3fMZZtAO!mPwrM|)C|eYD-SZiX_^rrl;CrT#AF7nauHPxg(Ys!_vaHj-WJ}3e;|*} zmu@`n;{J-|bZF{G1sQ>Rc7CJ4NgtMtcRKI_* z-Nt-*)9(Ht!ayb+>9=7_-hij%mSCOFtBnkmv{R9W8t-uf2d*qiDxfYIs5t3UDqoVK zl64_lp=SnvNIsHj9;Q8n)>W8BR~|0g2$gn4W1P?Y&inyiQrG=)^IUO*C~{f9=c=Ba zN9C~D$tQJ&0rrU=BP4e%h}d7Ar()t=Efs35)seh4&Pu~==Ng>2^k8pean_$*$RQ>` zaIA^^%G2!(1Wsg<-qKL01bq%d7dd&f*&_)ni&n+4YQ{)3kIPBDQJwl%7)e_PnbtZ}NM?*|WnfFHww6&DG-)benl-9Rz;XIyX#Bol&d$GKbl~ z?lX=vi7W6mBE6vEtfANXsBcT%ir9<83oCCaRcT7%ccRNcuF` zj(dOFC?KwTM6t;bT8;mJpXuBp`Eafws3o$qQT6plNFav2FaKnP4RqgERxxA(=Iil| zo$<3bdFI2)((F(R8C6rwd!qrCc;j@NG1RqW6-q=j=8lLWHtaPg!lgRz*&zF>5*R_y zAI>ykp>qkp40q;*rdzWxw=rSOFPQx(_dv_R$nP>PsaC3A$A)^zPbuf2023R>g07ajGX&lg?7jn-<3rc0P*G~(dpJ+bhsyUeo< zc~e5^ZsG;t)I_v*4>m}2+gs&SH&FnsK#1 znljegkY|q!SX<*nVQ%TB6Zob%+jD{YGD+RJ*?gSHsMg$nA!jN~?L{>q5Y!(Q#N`S|mjZ{`wbyKvmWo3fYR z+0#Rz+Y7h5WcOW-&vLH))g6~roqz*B(>Y>OW*a~`*W2DeTdE=Wa$@S-p64K$iE1Jr zr0>X~aVz&OCVi`VzckZNt&NgHRe#_F75vV}I365kFMtn~c-~yBV%D?J#=jb9BZxH~ zcT#Mcrl9rkHU5@$N;!g&Gc~-*}Wk{(c zjglX3RviQQmHO-Lt0-G`zc8&XkTqS*Z75cJO_2Hb=#7(M9V~M~|5wKV^g=MSzDOK;hgfVZ>SV>*duZ{H3=g!!60M zYkmIRk=6J>%CAy4wIfKvt%Ic{9_6x#kHO9@ySRmpaJ5p zc2)g=w^EQK*_?$Lkcl|*G%Hoh4qOtiPr(##ym|D^)~9zR!4l?r{Znxrnrv!vro*bk zSidFbNCGqJ81G9YqGv}u5fS*%~=b1%ZD0!O%RJm&y43h zzc(@dGF|om;R`VIt_+fU9k82cwZTC&vw!Wa$~)@3MqBV`>@mtApMGKgQ+L-E?$0>F3D&$}8;zng zI1YX6+|?GgeL8L#Q#>&$Zm)w2MBFqt6rV&8cInM4wp0kJ8cC=fwet37|I5Aa7DtH% zE{}NxzpLW&6_Da<|*1rky`=#HPT7@k?u1+VLjKkht`P(*?|>OFpuc%{UNd(p%_F7OR~}H zxwMNc4OZMQd@nN^pi97;|;c$+?16GPbIQ!6Nrcy*D}Q? z+pGpek={4mXu?a29QvOPzWMJ`E5}+0WtEqrFVHFY82W3=B3Zphtf`*&ZU@PAnmf;z zJgfB5{e;`?-2B|2)Tu6tYpQB{Acp*ny!khH!g7I(^w<0^Ne0TD8+k;AP5>0KSrs_G zss1~FGctL`9vKMDlVEkzB3pu4vsT;ch-95iDc*P$Zf>k0_hCxk&^0uW?yj?o0rJEZ zuEBHv1juVVclzYbNLro=I;|>lB#)NXhD+mtwa%&k9_zNVQnTft7>pop<#cTl@znLl z72wGDcTNgJeqwHRUq&_Z;5{kZtPeap-5Lo&-yC`T%~!V?|ETB1-Sy*pVXPe{;{led z#!Xu$qsiXj>c&S=^DRq>xdvpjo}pf^U7T8BA4qXMN%0!%qhj&Jv7&d45D+JCtuCo>>=lw!c>Lpm zIQxsSbLBF4qv6uQr~4mBDW@W>E@WA`O*grjvVLiIBaZIK8_RfnYT}jYh7Es}ifQ9{ zYX;BtixqSOmM6~sYP{`$KufF)Xk*l(kx>GDBDc+6?VC%ga^@CJmyvBN*GpU9bOFW16Ac)R*LV(E>FD;3G-`BU&YS$k=16!}`kd9L zB})f5E<;Gy#b?)8WH_G-R^1^#GSX|!>+x|%!D{GR)rMpVca>^!ANLg`k68{e(>+#P z>D#7*UzlGXt=kyrFam3xfF$M^GVnrY*lCLk(qnymgSxi4Q_=RZIw4uiChcfekKyd6FGEOff7~8_Kc5C;1BAYNsdIUxnzast8u%>xzS zufL78k)4Qyqim6mTlEvYY9263((xia|5S~MUg`b!>+#VG<06Yo_SMPpzn%WM(JJeJ zE3}l7+Y|0K7DC~1P1Jlf$lAETH*w4cRr0Ty?LxP^NB8}!NYZMM55Fv7G=3ohA6k$D zcJ#FWxT8fMrXiL;g!XH)jlE++Dx0wN!q-kF5+SVzIxJl*I^#qy<{VD64n0=vqzhLs za0t2c%*tHK;#Mj|!Se`v8Bm?>#gjtMOOyq9Bl<>CwSAsQIch7%CT4jKdYm*xT$m1J z#zSv>F?AA&=n##NHSjP&D0O8LNw3G-&D^{4A7yJTJLpJ2TfimRx;IBGX~NHQ>e|v2fLoUY!&utI@Kuj=BW#0sj1}P8%t++C5^Qknq^R*z07oP8CaszpP-=_-%3{ys_vL+-t=#Etb%&NqCzB|%N9#|wMbd+F>?b$3{1}LQ9(WVy z{UPPFR^HCZ@!-ck)4I^cZEea{Vy-u+hL*S3uiNa?zi%0~e8oJp|NStI(l(>`uJ8lr zzbehN98KsG@1^Gz=*wVIEFxvxx#QZ}?0p9EmLrSlu=Vm1%Az^9!zNHj{B0~k*%SNy3Ap^8 z@-xuG-DqUqp55UN%HKWG;XjAl(S#$yE+KCGOsmIc)ep@4B_CVQ-73|I2B!X>5@9LMiZhT+>zX7-yE|xUmxEj=>#X7 zGMO$_m>s={Uc#{dldKiWl88W(c)_cWnIuZ?#z84#?MdG9^?uL~KYu+CXM9Q6dDS!3 zrqsTMvL9*a>Q2l07Q8fDT~+a4a(ATDPX5zD-dySD#JSIYD~8iU4TW-aS~_ohvdK#k zS+(atu-%Q=g!$gw>vlsQ<42*Y-5^DQqkc1OD8QR@nWOV&45L4E0WXW{qWzLKO8} zv_!8JWuOp&MNUa>3Y@NX;p~Vy8vNT^*Zxo?19x$RScz881=cP14E@EcI?s_!dma%9 zhX`L}wAI#X4WqV=3k^k3d|iQzjG0M8tSxGHXhSxCj)R;pc|$Yftk$9;jmGszI#9~2 z^9{_CO=kE_w+~A^g*aJ^AU5RPkB!|j9vm0$u(ytBu=dN$>c*Kb+ce>5OrX%dHn;=6B7fav1(+9|Ly~)%3jT-!~Fx-B38TA7{Wo6i|ZFa zOVZI54N8TmeM2DHZr-`liwP&PCw7zp+FL@3zDQqt1(9q6)9(3xwy&SDVxs`UL+uVS%y@B=RDRA?b?S$f6^z zp7|BKj~l#t0weCoikYt_C>RyEUo$SmHlU}$Rc@~K@~tZyp_D_%<;KI7j?^wTtT1e; zigP_JTh3jtt^-|~@e$#;*FxUJ2w^_7*;l7;V~xK*x?ZVw|68dqlrFGBd%VciQA?P~ z*NyQOHS8J7U$x;exz@ET%Ir^%O?gsV?_=n3rh(f_l=hj%!`ae^L)o1UKv9_;1AqLa z10}zAXGeR83-B^wB|2dC3mN#2do)}mcFgqhmVAe9ZN+&>CvBNiW2ndKzwerveDiT3 z-h`68B|Nfny<;_NJ4ems&2yN-~Sqch&MT=g7F1 zay7*1&`+D|GieF_oLQNIiorCdKX-FWwzb;APg**%}|) zhy)%g+Vk%cnGGpqn>JC6Q1fO{Mo%8}%F6laZbSWm(|kkn|6Q_u_FMXdVeLcNjaU0q zb4M1ot>(d@mEEykKhH7fet~0?edJ=Y1?~dAZePGE$GID`0b54YT&mNPzgB2b7NoZa z9J?E&3a{)!aA)s5!;1}^PDb46$s~qeJ*)BzvgO!`7X-ehUbF`#rh@h)uz&Yf?3n?{ z6n)iaF7+3dTA66SO{x0H2y~fr%&k2;4vV=a3_SV)*Vpnv>94vNB)~bdcb%i~93{Jp zTwhBIxKvPC`ZKf6(jUUn!$V5XeMfRf(@g)aP)6EZ#5vw+!!z zfz;vo%SS1rGTBDU!6?`ZEkp6$qbK1zpD3~kwrl{o``(6!W$?k_sClNA;_?<|K`bJRinz;whZ|V z0biHA7%fG@Gjob7g$(CLfe<6mQA1gjMa8r2&0lGzmXE8)i1rBF;~gz&zwb;46Qmce zhDL~z5%?7}P1bH%R;LOrF`@X)HtTYr_Z6PV22)b=VW&ga^h9@+3u8I#bh)5 z`&!_nuCDr{ql&&wV?JG-xA|n5gwXM4K4=g{j9&()-*&J#fw?PNu48U|G_V6@Vra5U z-x50JBMvp$_DD2X-mgs5SAqjPZh(c$9Kz+D4t)=M3ppVhb%NkMpxt%#e&&VBPW|Ty zHsxWrL;q}?g4R-58T|s=JQ0O}`!PERALn3Fu3vLAdtPYOjbMUvk#Ma)A7^A3^z8|M;y)#A7`LnG%bEl3szn))PyzvGEom@P32>5 zdxcbm?S>tvbk8u5K)nXB{r)}kk7C}`HYEH+eP#|u5`CcqMYHE7M`_TWc^?kySO=AZP+3dmJKN8v2tc1_D`%2zddFA>woW7 zkD&A4pS+tHW<2J;S?gUoDc0L?ZEi>Toi=jsBbNNN#CJ1J+IaLVy|yx_(=fpJ^vmro zqLaeVKyGo#Aju2f{5Y)3=zR(B>;;{m!ztciWI!Wwe)0aPGjoD>tABTIMB&GZ!mL5! z%3F80uqZY5QaObZR2A0V>|R-(Fs*$USA-3qI28g16%vOJq%P5JY4?~V8kCCHrFd=! zZdm_4XCdJpa#e1vfjh8ckc&%aMof?)N8?eUN^8!#+KN}|X%Rb2Fd62QqR*nxU{0-* z-Tu^gqON_Fa@T`vEFnklyS#SMz%AR{Xt5IXxD3M;X8Yb)f~)Sqi@V=!+UdDEtJvea zBVXRZ6`89|)T3awVv~6Tiq-+w1n*YfT@inM5(`cCys!A2R^=xK^E`o>A?pRKP=3I| zY5#FKPfmEEzT@X)SS+N5aKa{I`y&mYHp8l6gI){MFmK6SIBQc~>qMlniI}D0^C17>dRfbx0 z>nlExP1hAkNT|kBFa!TYh>k?!E~7R(VPZ=>60dQ@uizHC&9 zIxpw^f;B%b(s7pDOn7Er+ML9#Sbu~3!=)~(_;YIIDRz)Vh9xHAtJl0)F30cZ$`pQ9 zr#AmPyZv%e8uOdZ1qM)L-CkX$l)W^O@6{dNr^V(ik&zo^%YqQIsdddpJLWPS{H2a7 z*&#R>6I8IsTb}`suN%-Pc}2f0VlaBlY|ugcnGyuS`lr#{h~jS + + + cat.gif + cat.gif + cat.gif + REST API + From API + + 15843.c49f68ff81b552d315927df2e27df506 + + diff --git a/extra/api/folder.xml b/extra/api/folder.xml new file mode 100644 index 00000000..fd0bef9c --- /dev/null +++ b/extra/api/folder.xml @@ -0,0 +1,6 @@ + + + rest_api + A folder created via REST API + + diff --git a/init.rb b/init.rb index eb332460..5097a868 100644 --- a/init.rb +++ b/init.rb @@ -28,7 +28,7 @@ Redmine::Plugin.register :redmine_dmsf do name 'DMSF' author 'Vit Jonas / Daniel Munn / Karel Picman' description 'Document Management System Features' - version '1.5.1 stable' + version '1.5.2 devel' url 'http://www.redmine.org/plugins/dmsf' author_url 'https://github.com/danmunn/redmine_dmsf/graphs/contributors' @@ -74,7 +74,7 @@ Redmine::Plugin.register :redmine_dmsf do {:dmsf => [:new, :create, :delete, :edit, :save, :edit_root, :save_root, :lock, :unlock, :notify_activate, :notify_deactivate, :restore]} permission :file_manipulation, {:dmsf_files => [:create_revision, :lock, :unlock, :delete_revision, :notify_activate, :notify_deactivate, :restore], - :dmsf_upload => [:upload_files, :upload_file, :commit_files], + :dmsf_upload => [:upload_files, :upload_file, :upload, :commit_files, :commit], :dmsf_links => [:new, :create, :destroy, :restore] } permission :file_delete, diff --git a/lib/redmine_dmsf/webdav/base_resource.rb b/lib/redmine_dmsf/webdav/base_resource.rb index 7b5bf7ae..b91a9786 100644 --- a/lib/redmine_dmsf/webdav/base_resource.rb +++ b/lib/redmine_dmsf/webdav/base_resource.rb @@ -78,7 +78,7 @@ module RedmineDmsf '-', '', '', - ] + entities if parent? + ] + entities if parent @response.body << index_page % [ path.empty? ? '/' : path, path.empty? ? '/' : path , entities ] end diff --git a/lib/tasks/dmsf_convert_documents.rake b/lib/tasks/dmsf_convert_documents.rake index 82e0321f..7df67efd 100644 --- a/lib/tasks/dmsf_convert_documents.rake +++ b/lib/tasks/dmsf_convert_documents.rake @@ -141,19 +141,12 @@ class DmsfConvertDocuments revision.comment = 'Converted from documents' revision.mime_type = attachment.content_type - revision.disk_filename = revision.new_storage_filename - attachment_file = File.open(attachment.diskfile, 'rb') + revision.disk_filename = revision.new_storage_filename unless dry - File.open(revision.disk_file, 'wb') do |f| - while (buffer = attachment_file.read(8192)) - f.write(buffer) - end - end + FileUtils.cp(attachment.diskfile, revision.disk_file) revision.size = File.size(revision.disk_file) - end - - attachment_file.close + end if dry puts "Dry check revision: #{revision.title}"