diff --git a/CHANGELOG.md b/CHANGELOG.md index 4745aaa..501d431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Enhancements - **Folder Changes** - Folders are now sorted alphabetically in each category + - All files in a folder can be downloaded as a single zip file - **Document Changes** - Reduced size of folder names in documents sidebar - Admins can now set keywords for documents to improve search results for diff --git a/Gemfile b/Gemfile index a087657..30dc6d9 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,7 @@ gem "kaminari", "~> 1.1.1" gem "mini_magick", "~> 4.9.2" gem "pg_search", "~> 2.3.0" gem "redcarpet", "~> 3.5.0" +gem "rubyzip", "~> 1.2.3" # Rails defaults gem "coffee-rails", "~> 5.0" diff --git a/app/controllers/folders_controller.rb b/app/controllers/folders_controller.rb index 54fa32f..d38c4cc 100644 --- a/app/controllers/folders_controller.rb +++ b/app/controllers/folders_controller.rb @@ -9,7 +9,7 @@ class FoldersController < ApplicationController ] before_action :create_category, only: [:create, :update] before_action :find_category_and_folder_or_redirect, only: [ - :show, :edit, :upload + :show, :edit, :upload, :download_zip ] before_action :find_folder_or_redirect, only: [ :update, :destroy, :attach_file, :attach_files @@ -27,6 +27,18 @@ def show @documents = docs_scope_order(@folder.documents).page(params[:page]).per(Folder::DOCS_PER_PAGE) end + # GET /docs/:category/:folder/download-zip + def download_zip + @folder.generate_zipped_folder! + filename = "lofthf-study-#{@folder.category.slug}-#{@folder.slug}.zip" + + if Rails.env.production? + redirect_to @folder.zipped_folder.url(query: { "response-content-disposition" => "attachment" }), allow_other_host: true + else + send_file_if_present @folder.zipped_folder, filename: filename + end + end + # GET /docs/new def new category = Category.find_by_param(params[:category]) diff --git a/app/models/folder.rb b/app/models/folder.rb index 7a6d8e9..58e0391 100644 --- a/app/models/folder.rb +++ b/app/models/folder.rb @@ -2,6 +2,9 @@ # Tracks files uploaded to the folder. class Folder < ApplicationRecord + # Uploaders + mount_uploader :zipped_folder, ZipUploader + # Constants DOCS_PER_PAGE = 20 @@ -32,4 +35,31 @@ def attach_file!(file) content_type: Document.content_type(file.original_filename) ) end + + def generate_zipped_folder! + zip_name = "folder.zip" + temp_zip_file = Tempfile.new(zip_name) + begin + # Initialize temp zip file. + Zip::OutputStream.open(temp_zip_file) { |zos| } + # Write to temp zip file. + Zip::File.open(temp_zip_file, Zip::File::CREATE) do |zip| + documents.each do |document| + # Two arguments: + # - The name of the file as it will appear in the archive + # - The original file, including the path to find it + zip.add(document.filename, document.file.path) if File.exist?(document.file.path) && File.size(document.file.path).positive? + end + end + temp_zip_file.define_singleton_method(:original_filename) do + zip_name + end + update zipped_folder: temp_zip_file + # update file_size: zipped_folder.size # Cache after attaching to model. + ensure + # Close and delete the temp file + temp_zip_file.close + temp_zip_file.unlink + end + end end diff --git a/app/uploaders/zip_uploader.rb b/app/uploaders/zip_uploader.rb new file mode 100644 index 0000000..915f464 --- /dev/null +++ b/app/uploaders/zip_uploader.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# Allows a zip file to be uploaded. +class ZipUploader < CarrierWave::Uploader::Base + # Choose what kind of storage to use for this uploader: + storage Rails.env.production? ? :fog : :file + + # Override the directory where uploaded files will be stored. + # This is a sensible default for uploaders that are meant to be mounted: + def store_dir + File.join(model.class.to_s.underscore.pluralize, model.id.to_s, mounted_as.to_s) + end + + # Provide a default URL as a default if there hasn't been a file uploaded: + # def default_url + # "/assets/fallback/" + [version_name, "default.png"].compact.join('_') + # end + + # Add a white list of extensions which are allowed to be uploaded. + def extension_whitelist + %w(zip) + end + + # Override the filename of the uploaded files: + # Avoid using model.id or version_name here, see uploader/store.rb for details. + # def filename + # "something.jpg" if original_filename + # end +end diff --git a/app/views/folders/show.html.haml b/app/views/folders/show.html.haml index 733589a..1b71004 100644 --- a/app/views/folders/show.html.haml +++ b/app/views/folders/show.html.haml @@ -14,6 +14,10 @@ %li= link_to "docs", folders_path %li.breadcrumb-muted= @category.slug %li.breadcrumb-muted= @folder.slug + %li.breadcrumb-muted + = link_to download_zip_category_folder_path(@folder.category, @folder), method: :post do + = icon("fas", "file-download") + download all - if current_user.editor? %li = link_to edit_category_folder_path(@category, @folder) do diff --git a/config/initializers/app_config.rb b/config/initializers/app_config.rb new file mode 100644 index 0000000..06e5e53 --- /dev/null +++ b/config/initializers/app_config.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "zip" diff --git a/config/routes.rb b/config/routes.rb index 53f4a27..b5ba6f4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -34,6 +34,7 @@ get "docs/:category/:folder", to: "folders#show", as: :category_folder get "docs/:category/:folder/edit", to: "folders#edit", as: :edit_category_folder get "docs/:category/:folder/upload", to: "folders#upload", as: :upload_category_folder + post "docs/:category/:folder/download-zip", to: "folders#download_zip", as: :download_zip_category_folder get "docs/:category/:folder/*filename", to: "documents#download", as: :download_document, format: false get "docs/:category", to: redirect("docs"), as: :docs_category diff --git a/db/migrate/20190926234257_add_zipped_folder_to_folders.rb b/db/migrate/20190926234257_add_zipped_folder_to_folders.rb new file mode 100644 index 0000000..284abc2 --- /dev/null +++ b/db/migrate/20190926234257_add_zipped_folder_to_folders.rb @@ -0,0 +1,5 @@ +class AddZippedFolderToFolders < ActiveRecord::Migration[6.0] + def change + add_column :folders, :zipped_folder, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index d994628..662bb4e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_09_04_004415) do +ActiveRecord::Schema.define(version: 2019_09_26_234257) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -79,6 +79,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "documents_count", default: 0, null: false + t.string "zipped_folder" t.index ["archived"], name: "index_folders_on_archived" t.index ["category_id", "name"], name: "index_folders_on_category_id_and_name", unique: true t.index ["category_id", "slug"], name: "index_folders_on_category_id_and_slug", unique: true @@ -141,7 +142,7 @@ t.string "header_label" t.jsonb "header" t.datetime "last_cached_at" - t.boolean "archived" + t.boolean "archived", default: false, null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.bigint "project_id"