Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a013bd6
Virtual Collection Draft
MontrealSergiy Sep 16, 2024
7b12306
Virtual Collection Draft
MontrealSergiy Sep 16, 2024
a8a0d8f
Virtual Collection minor improvement to a userfile helper
MontrealSergiy Sep 23, 2024
f9eb56a
Virtual Collection render collection tree
MontrealSergiy Sep 23, 2024
5982f47
remove debug info
MontrealSergiy Sep 24, 2024
b268c80
add virtual collection creation to menu, minor corrections #1135
MontrealSergiy Oct 2, 2024
f051e5d
remove a debug statement #1135
MontrealSergiy Oct 4, 2024
9143d34
hide the checkboxes from virtual collection view #1135
MontrealSergiy Oct 8, 2024
bc51f2e
redirect to created virtual collection view #1135
MontrealSergiy Oct 8, 2024
50bc2f9
checkpoint - validation and view #1135
MontrealSergiy Oct 15, 2024
045139f
and a simple non-tree viewer #1135
MontrealSergiy Oct 16, 2024
3108108
add a non-tree viewer to virtual collection #1135
MontrealSergiy Oct 16, 2024
f01c949
extract userfile for virtual collection checkpoint
MontrealSergiy Oct 21, 2024
056a07b
extract userfiles for virtual collection #1135
MontrealSergiy Oct 24, 2024
e32b6ff
minor fixes and cleaning Virtural Collection #1135
MontrealSergiy Oct 28, 2024
591d892
refactor userfile getter of Virtual Collection #1135
MontrealSergiy Oct 28, 2024
dd6053b
no CBRAIN_ARCHIVE_CONTENT_BASENAME Virtual Collection #113
MontrealSergiy Oct 28, 2024
62f138f
improve a controller comment Virtual Collection #113
MontrealSergiy Oct 28, 2024
6d3b3a7
undo irrelevant reformat Virtual Collection #1135
MontrealSergiy Oct 28, 2024
3daf39b
unroll checkbox column deletion for dir tree browsing Virtual Collec…
MontrealSergiy Oct 28, 2024
ef7401b
minor view improvements Virtual Collection #1135
MontrealSergiy Oct 28, 2024
64528f5
forbid Virtual Collection inside Collections (as of now user will get…
MontrealSergiy Oct 29, 2024
5eae85b
remove direct CivetVirtual study type checking Virtual Collection #1135
MontrealSergiy Oct 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 164 additions & 2 deletions BrainPortal/app/controllers/userfiles_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class UserfilesController < ApplicationController
around_action :permission_check, :only => [
:download, :update_multiple, :delete_files,
:create_collection, :change_provider, :quality_control,
:export_file_list
:export_file_list, :create_virtual_collection
]

MAX_DOWNLOAD_MEGABYTES = 400
Expand Down Expand Up @@ -1001,6 +1001,15 @@ def create_collection #:nodoc:
return
end

virtual_files = FileCollection.where(id: filelist, type: ['CivetVirtualStudy', 'VirtualFileCollection']).to_a

if virtual_files.present?
virtual_files = Userfile.find_accessible_by_user(virtual_files.pluck(:id), current_user, :access_requested => :read)
flash[:error] = "Collections of Virtual Collection are not allowed. Exclude #{virtual_files.map(&:name).to_sentence} " if virtual_files
redirect_to :action => :index, :format => request.format.to_sym
return
end

collection = FileCollection.new(
:user_id => current_user.id,
:group_id => file_group,
Expand Down Expand Up @@ -1040,6 +1049,84 @@ def create_collection #:nodoc:

end

# Create a virtual collection from the selected files.
def create_virtual_collection #:nodoc:
filelist = params[:file_ids].uniq || []
data_provider_id = params[:data_provider_id_for_collection]
collection_name = params[:collection_name]
file_group = current_assignable_group.id

if data_provider_id.blank?
flash[:error] = "No data provider selected.\n"
redirect_to :action => :index
return
end

# Handle collection name
if collection_name.blank?
suffix = Time.now.to_i
while Userfile.where(:user_id => current_user.id, :name => "VirtualCollection-#{suffix}").first.present?
suffix += 1
end
collection_name = "VirtualCollection-#{suffix}"
end

if ! Userfile.is_legal_filename?(collection_name)
flash[:error] = "Error: collection name '#{collection_name}' is not acceptable (illegal characters?)."
redirect_to :action => :index, :format => request.format.to_sym
return
end

# Check if the collection name chosen by the user already exists for this user on the data_provider
if current_user.userfiles.exists?(:name => collection_name, :data_provider_id => data_provider_id)
flash[:error] = "Error: collection with name '#{collection_name}' already exists."
redirect_to :action => :index, :format => request.format.to_sym
return
end

userfiles = Userfile.find_accessible_by_user(filelist, current_user, :access_requested => :read)

# todo double check how 0 is possible, bad files should cause exception
if userfiles.count == 0
flash[:error] = "Error: Inaccessible files selected."
redirect_to :action => :index, :format => request.format.to_sym
return
end

collection = VirtualFileCollection.new(
:user_id => current_user.id,
:group_id => file_group,
:data_provider_id => data_provider_id,
:name => collection_name
)

collection.save!
collection.cache_prepare
coldir = collection.cache_full_path
Dir.mkdir(coldir)

collection.set_virtual_file_collection(userfiles)

# Save the content and DB model

collection.sync_to_provider
collection.save
collection.set_size

# Find the files
userfiles = Userfile
.find_all_accessible_by_user(current_user, :access_requested => :read)
.where(:id => filelist).all.to_a

if userfiles.empty?
flash[:error] = "You need to select some files first."
redirect_to(:action => :index)
return
end
redirect_to(:controller => :userfiles, :action => :show, :id => collection.id)

end

# Copy or move files to a new provider.
def change_provider #:nodoc:

Expand Down Expand Up @@ -1206,7 +1293,82 @@ def download #:nodoc:
end
end

#Extract a file from a collection and register it separately
# Extract files from a virtual collection and register them separately
# in the database.
def extract_from_virtual_collection #:nodoc:
success = failure = 0

unless params[:collection_files] && params[:collection_files].size > 0
flash[:notice] = "No files selected for extraction"
redirect_to :action => :show
return
end

collection_ids_file_pairs = params[:collection_files].map {|x| x.split('#', 2)}
collections_file_names = collection_ids_file_pairs.group_by { |first, _| first }
# add the object to hash and validate provider access, while keeping primitive keys

# todo - add option to specify target data_provider
collections_file_names.each do |collection_id, obj_file_list|
collection = FileCollection.find_accessible_by_user(collection_id, current_user, :access_requested => :read)
obj_file_list.each { |pair| pair[0] = collection }
data_provider = collection.data_provider
if data_provider.read_only?
flash[:error] = "Unfortunately file #{obj_filelist.second.first} of collection #{collection.name} is located on a not writable DataProvider #{data_provider.name}, so we can't extract its internal files."
redirect_to :action => :show
return
end
end

results = collections_file_names.map do |collection_id, collection_file_pairs|
collection = collection_file_pairs.first.first
data_provider = collection.data_provider
collection_path = collection.cache_full_path
collection_file_pairs.map do |_, file| # Extract each file
# Validations; make sure "file" is a path inside the collection
rel_path = Pathname.new(file)
next :not_relative unless rel_path.relative?
full_path = collection_path.parent + rel_path
full_path = File.realpath(full_path.to_s) rescue nil
next :not_resolve unless full_path
next :is_symlink if File.symlink?(full_path.to_s)
next :not_file unless File.file?(full_path.to_s)
next :outside_col unless full_path.start_with? collection_path.to_s
basename = rel_path.basename.to_s
file_type = Userfile.suggested_file_type(basename) || SingleFile
userfile = file_type.new(
:name => basename,
:user_id => current_user.id,
:group_id => collection.group_id,
:data_provider_id => data_provider.id
)
Dir.chdir(collection_path.parent) do
next :cannot_save_userfile unless userfile.save
userfile.addlog("Extracted from collection '#{collection.name}'.")
begin
userfile.cache_copy_from_local_file(full_path.to_s)
next :ok
rescue
userfile.data_provider_id = nil # nullifying will skip the provider_erase() in the destroy()
userfile.destroy
next :exception_copy
end
end
end
end
success = results.flatten.count { |x| x == :ok }
failure = results.flatten.size - success
if success > 0
flash[:notice] = "#{success} files were successfully extracted."
end
if failure > 0
flash[:error] = "#{failure} files could not be extracted."
end
redirect_to :action => :index
end


#Extract files from a collection and register it separately
#in the database.
def extract_from_collection #:nodoc:
success = failure = 0
Expand Down
4 changes: 2 additions & 2 deletions BrainPortal/app/helpers/userfiles_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,11 @@ def data_link(file_name, userfile, replace_div_id="sub_viewer_filecollection_cbr
end
elsif display_name =~ /\.html$/i # TODO: this will never happen if we ever create a HtmlFile model with at least one viewer
link_to "#{display_name}",
stream_userfile_path(@userfile, :file_path => file_name, :disposition => 'inline'),
stream_userfile_path(userfile, :file_path => file_name, :disposition => 'inline'),
:target => '_BLANK'
else
link_to h(display_name),
url_for(:action => :content, :content_loader => :collection_file, :arguments => file_name)
url_for(:action => :content, :id => userfile.id, :content_loader => :collection_file, :arguments => file_name)
end
end

Expand Down
30 changes: 30 additions & 0 deletions BrainPortal/app/views/userfiles/_dialogs.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,36 @@
</form>
</div>

<div id="virtual-collection-dialog" class="dlg-dialog" title="New virtual collection">
<form action="<%= create_virtual_collection_userfiles_path %>" method="post">
<label for="co-name" class="dlg-fld-lbl">Name</label>
<input id="co-name"
class="dlg-fld"
type="text"
name="collection_name"
value="NewVirtualCollection"
/>
<span id="co-invalid-name" class="dlg-msg-err">
&#9888; Invalid!
</span>
<br />

<label for="co-dp" class="dlg-fld-lbl">Provider</label>
<%=
data_provider_select('data_provider_id_for_collection',
{ :data_providers => writable_dps },
{
:id => 'co-dp',
:class => 'dlg-fld',
:'data-placeholder' => "A data provider..."
}
)
%>
<br /><br />
</form>
</div>


<div id="delete-confirm"
class="dlg-dialog dlg-cfrm"
title="Delete"
Expand Down
11 changes: 11 additions & 0 deletions BrainPortal/app/views/userfiles/_file_menu.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@
</li>
<li class="menu-separator dyn-item">-</li>

<li id="col-item"
class="act-item dyn-item"
data-dialog="virtual-collection-dialog"
>
<div>
New virtual collection
<span class="ui-icon ui-icon-folder-collapsed"></span>
</div>
</li>
<li class="menu-separator dyn-item">-</li>

<li id="exp-item"
class="act-item"
data-url="<%= userfiles_path(:format => :csv) %>"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@

<%-
#
# CBRAIN Project
#
# Copyright (C) 2008-2012
# The Royal Institution for the Advancement of Learning
# McGill University
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
#
-%>

<% if @userfile.is_locally_synced? %>

<% @cbfl = @userfile.cached_cbrain_file_list %>

<p class="medium_paragraphs">
This Virtual File Collection consists of other CBRAIN files and collections
listed below. This simple view does allow you to explore sub-directories or
subfiles, for that you still can use Virtual File Collection view.
Note you only see files that you have assess too.
<p class="medium_paragraphs"> This is an experimental feature.
The files and collection could change name or content over time, potentially making collection unusable.
In the
table below, the names of the files are repeated: the first one is the name as
known within CBRAIN, and the second one is the original name of file when collection
was created.
</p>

<%
csv_array = @cbfl.cached_csv_array
per_page = 500
nb_row = csv_array.size
page = (params[:page] || 1).to_i
page = 1 if page < 1
csv_array = WillPaginate::Collection.create(page, per_page) do |pager|
pager.replace(csv_array[(page-1)*per_page, per_page] || [])
pager.total_entries = csv_array.size
pager
end
%>

<div class="no_ajax_pagination">
<span>
<%= will_paginate csv_array,
:params => { :controller => :userfiles, :action => :show, :sort_index => @sort_index },
:container => false
%>
(<%= pluralize nb_row, "file" %> in this list)
</span>
</div>

<table id="report_table">

<%
################################
# Top headers of table
################################
%>

<tr>
<% attlist = @cbfl.class.const_get('ATTRIBUTES_LIST') %>
<% attlist.each do |att| %>
<% att = :project if att == :group_id %>
<th><%= att.to_s.sub(/_id\z/,"").classify.gsub(/(.+)([A-Z])/, '\1 \2') %></th>
<% end %>
</tr>

<%
################################
# Main body of table
################################
%>

<% csv_array.each do |cvs_row| %>
<tr>
<% cur_file = nil %>
<% attlist.each_with_index do |att,idx|%>
<% val = cvs_row[idx] %>
<td class="left_align">
<% if att == :id %>
<% cur_file = Userfile.find_all_accessible_by_user(current_user, :access_requested => :read).where(:id => val).first %>
<%= val %> : <%= link_to_userfile_if_accessible(cur_file) %>
<% else %>
<%= val.nil? ? "-" : val %>
<% end %>
</td>
<% end %>
</tr>
<% end %>

</table>
<p></p>
<%= link_to 'Extract a file list', { :action => 'export_file_list', :file_ids => @userfile.get_userfiles.pluck(:id) }, :class => "button", :method => :post %>

<% end %>
Loading