Commit 28025946 authored by Francisco Juan's avatar Francisco Juan

add redmine_repository plugin for downloading repositories

parent b4cdfcdc
......@@ -103,3 +103,5 @@ end
instance_eval File.read(file)
end
end
gem "rubyzip"
......@@ -89,6 +89,7 @@ DEPENDENCIES
rmagick (>= 1.15.17)
ruby-openid (~> 2.1.4)
rubytree (~> 0.5.2)
rubyzip
shoulda (~> 2.10.3)
sqlite3
sqlite3-ruby (< 1.3)
= repository
This plugin allows to download files and folders via one zip archive.
= installation
1. Copy plugin folder into vendor/plugins in your redmine directory.
1. Install rubyzip gem (gem install rubyzip).
2. Restart Redmine.
3. Enable option "Download files in one zip archive" for roles in administration page.
require 'zip/zip'
require 'zip/zipfilesystem'
class RepositoryZip
attr_reader :file_count
def initialize()
@zip = Tempfile.new(["repository_zip",".zip"])
@zip_file = Zip::ZipOutputStream.new(@zip.path)
@file_count = 0
end
def finish
@zip_file.close unless @zip_file.nil?
@zip.path unless @zip.nil?
end
def close
@zip_file.close unless @zip_file.nil?
@zip.close unless @zip.nil?
end
def add_file(file, cat)
@zip_file.put_next_entry(file)
@zip_file.write(cat)
@file_count += 1
end
def add_folder(folder)
@zip_file.put_next_entry(folder + "/")
end
end
<% form_tag({:action => "entries_operation"}, :method => :post, :id => "Entries") do %>
<%= hidden_field_tag("action") %>
<%= hidden_field_tag("path[]", @path) %>
<table class="list entries" id="browser">
<thead>
<tr id="root">
<th>
<%= check_box_tag('check_tree', 'check_tree', false, :style => "float: left", :onClick => "checkTree();") if authorize_for('repositories', 'entries_operation')%>
<%= l(:field_name) %></th>
<th><%= l(:field_filesize) %></th>
<th><%= l(:label_revision) %></th>
<th><%= l(:label_age) %></th>
<th><%= l(:field_author) %></th>
<th><%= l(:field_comments) %></th>
</tr>
</thead>
<tbody>
<%= render :partial => 'dir_list_content' %>
</tbody>
</table>
<% if authorize_for('repositories', 'entries_operation') %>
<div style="float: right;">
<%= submit_tag(l(:Download), :name => "download_entries") %>
<%= submit_tag("Email", :name => "email_entries", :style => "visibility: collapse") %>
</div>
<% end %>
<p>&nbsp</p>
<% end %>
<% @entries.each do |entry| %>
<% tr_id = Digest::MD5.hexdigest(entry.path)
depth = params[:depth].to_i
params[:parent_val] = entry.path %>
<tr id="<%= tr_id %>" class="<%= h params[:parent_id] %> entry <%= entry.kind %>">
<td style="padding-left: <%=18 * depth%>px;" class="filename">
<% if entry.is_dir? %>
<span class="expander" onclick="<%= remote_function :url => {:action => 'show', :id => @project, :path => to_path_param(entry.path), :rev => @rev, :depth => (depth + 1), :parent_id => tr_id, :parent_val => entry.path},
:method => :get,
:update => { :success => tr_id },
:position => :after,
:success => "scmEntryLoaded('#{tr_id}')",
:complete => "checkBranch('#{tr_id}', getParentNodeChecked('#{params[:parent_val]}'))",
:condition => "scmEntryClick('#{tr_id}')"%>">&nbsp</span>
<span><%= check_box_tag("folders[]", entry.path, false, :id => params[:parent_id], :onclick => "checkBranch('#{tr_id}', this.checked);" ) if authorize_for('repositories', 'entries_operation') %></span>
<% else %>
<span style="padding-left: 8px">&nbsp</span>
<span><%= check_box_tag("files[]", entry.path, false, :id => params[:parent_id]) if authorize_for('repositories', 'entries_operation') %></span>
<% end %>
<%= link_to h(entry.name),
{:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :path => to_path_param(entry.path), :rev => @rev},
:class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(entry.name)}")%>
</td>
<td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
<td class="revision"><%= link_to_revision(changeset, @project) if changeset %></td>
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
<td class="author"><%= changeset.nil? ? h(entry.lastrev.author.to_s.split('<').first) : changeset.author if entry.lastrev %></td>
<td class="comments"><%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %></td>
</tr>
<% end %>
<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %>
<%= flash.discard %>
<div class="contextual">
<%= render :partial => 'navigation' %>
</div>
<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %></h2>
<% if !@entries.nil? && authorize_for('repositories', 'browse') %>
<%= render :partial => 'dir_list' %>
<% end %>
<%= render_properties(@properties) %>
<% if @changesets && !@changesets.empty? && authorize_for('repositories', 'revisions') %>
<h3><%= l(:label_latest_revision_plural) %></h3>
<%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => nil }%>
<% if @path.blank? %>
<p><%= link_to l(:label_view_all_revisions), :action => 'revisions', :id => @project %></p>
<% else %>
<p><%= link_to l(:label_view_revisions), :action => 'changes', :path => to_path_param(@path), :id => @project %></p>
<% end %>
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :action => 'revisions', :id => @project, :page => nil, :key => User.current.rss_key})) %>
<% end %>
<% other_formats_links do |f| %>
<%= f.link_to 'Atom', :url => {:action => 'revisions', :id => @project, :key => User.current.rss_key} %>
<% end %>
<% end %>
<% content_for :header_tags do %>
<%= stylesheet_link_tag "scm" %>
<%= stylesheet_link_tag 'repository.css', :plugin => "redmine_repository", :media => 'all' %>
<%= javascript_include_tag "repository.js", :plugin => "redmine_repository" %>
<% end %>
<% html_title(l(:label_repository)) -%>
function getParentNodeChecked(parentVal)
{
var form = document.getElementById('Entries');
for (var i=0;i<form.elements.length;i++)
{
var e = form.elements[i];
if(e.type=='checkbox' && e.value == parentVal)
return e.checked;
}
}
function checkTree()
{
var form = document.getElementById('Entries');
for (var i=0;i<form.elements.length;i++)
{
var e = form.elements[i];
if ((e.name != 'check_tree') && (e.type=='checkbox'))
e.checked = form.check_tree.checked;
}
}
function checkBranch(parentId, checked)
{
var allchecked = true;
var has_check_tree = false;
var form = document.getElementById('Entries');
for (var i=0;i<form.elements.length;i++)
{
var e = form.elements[i];
if(e.type=='checkbox')
{
if(e.id == parentId)
{
e.checked = !checked;
e.click();
}
allchecked = allchecked && e.checked;
if(e.name == "check_tree") has_check_tree = true;
}
}
if(has_check_tree)
form.check_tree.checked = allchecked;
}
#svn
{
background-image: url('../images/svn.gif');
background-repeat: no-repeat;
height: 16px;
background-position: 0 50%;
padding-left: 20px;
}
# Bulgarian translation by Ivan Cenov
# i_cenov@botevgrad.com
bg:
Download: "Изтегляне"
warning_no_entries_selected: "Не е избрано нищо за изтегляне."
error_in_getting_files: "Грешка при четене на файловете"
permission_operations: "Групово изтегляне на файлове в един zip архив"
# English strings go here for Rails i18n
en:
Download: "Download"
warning_no_entries_selected: "No entries selected"
error_in_getting_files: "Error when getting files"
permission_operations: "Download files in one zip archive"
# English strings go here for Rails i18n
ru:
Download: "Скачать"
warning_no_entries_selected: "Ничего не выбрано"
error_in_getting_files: "Ошибка при доступе к файлам"
permission_operations: "Групповая загрузка файлов одним архивом"
ActionController::Routing::Routes.draw do |map|
map.connect 'projects/:id/repository', :controller => 'repositories', :action => 'entries_operation'
end
\ No newline at end of file
require 'redmine'
require 'dispatcher'
require 'repositories_controller_patch'
Redmine::Plugin.register :redmine_repository do
name 'Redmine Repository plugin'
author 'SaNNy'
description 'This is a reposirory plugin for Redmine. Semgroup customization.'
version '0.0.4'
requires_redmine :version_or_higher => '1.1.2'
end
Redmine::AccessControl.map do |map|
map.project_module :repository do |map|
map.permission :operations, :repositories => [:entries_operation]
end
end
# English strings go here
my_label: "My label"
require 'tree'
require_dependency 'application_controller'
require_dependency 'repositories_controller'
require_dependency 'repository_zip'
module RepositoriesControllerPatch
def self.included(base) # :nodoc:
base.extend(ClassMethods)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable # послать unloadable чтобы не перегружать при разработке
end
end
module ClassMethods
end
module InstanceMethods
def entries_operation
selected_folders = params[:folders].nil? ? [] : params[:folders]
selected_files = params[:files].nil? ? [] : params[:files]
if selected_folders.empty? && selected_files.empty?
redirect_to :action => "show", :id => @project, :path => @path
return
end
# make a selected files and folders tree
selected_tree = Tree::TreeNode.new(".", "root")
selected_files.each do |file|
folder = Pathname.new(file).dirname.to_s
selected_tree_node = selected_tree
if !folder.match(/^\.+$/)
folder.split("/").each do
if selected_tree_node[folder].nil?
selected_tree_node = selected_tree_node.add(Tree::TreeNode.new(folder, "folder"))
else
selected_tree_node = selected_tree_node[folder]
end
end
selected_tree_node << Tree::TreeNode.new(file, "file")
else
selected_tree << Tree::TreeNode.new(file, "file")
end
end
selected_folders.each do |folder|
selected_tree_node = selected_tree
folder.split("/").each do
if selected_tree_node[folder].nil?
selected_tree_node = selected_tree_node.add(Tree::TreeNode.new(folder, "folder"))
else
selected_tree_node = selected_tree_node[folder]
end
end
end
begin
if !params[:email_entries].blank?
email_entries(selected_tree)
else
download_entries(selected_tree)
end
rescue => e
flash[:warning] = l(:error_in_getting_files) + " (" + e.message + ")"
redirect_to :action => "show", :id => @project, :path => @path
end
end
def download_entries(selected_tree)
zip = RepositoryZip.new
zip_entries(zip, selected_tree)
send_file(zip.finish,
:filename => filename_for_content_disposition(@project.name + "-" + DateTime.now.strftime("%y%m%d%H%M%S") + ".zip"),
:type => "application/zip",
:disposition => "attachment")
ensure
zip.close unless zip.nil?
end
def zip_entries(zip, selected_tree)
selected_tree.children.each do |node|
if node.content == "file"
zip.add_file(node.name, @repository.cat(node.name, @rev))
else
zip.add_folder(node.name)
if node.hasChildren?
# add selected subfolders
zip_entries(zip, node)
else
# add all subfolders with files
entries = @repository.entries(node.name, @rev)
entries.each do |entry|
node << Tree::TreeNode.new(entry.path, entry.is_dir? ? "folder" : "file")
end
end
zip_entries(zip, node)
end
end
zip
end
end # of InstaceMethods
end # of module
RepositoriesController.send(:include, RepositoriesControllerPatch)
# Load the normal Rails helper
require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
# Ensure that we are using the temporary fixture path
Engines::Testing.set_fixture_path
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment