Upgrade to Rails 5.1

and fixed wiki versioning test

Rails 5.1 last steps

crabgrass_media from rubygems
remove controller test for task sorting - not possible to test xhr request without route in new parameter syntax
remove new framework defaults initializer file

Final step of upgrade to rails 5.1

we only test on stretch, because we need ruby >= 2.2.2
update dependencies by running bundle update
and fix gallery_image_controller_test

Also write db/schema.rb with rails 5
This commit is contained in:
dgt 2018-08-29 18:50:00 +02:00 committed by Azul
parent e853c2e9d9
commit 03a80251b5
44 changed files with 630 additions and 2777 deletions

View File

@ -22,30 +22,6 @@ bundle_audit:
bundle-audit update
bundle-audit check
test:
stage: test
services:
- mysql:5.5
script: |
bundle exec rake create_a_secret
cp config/database.yml.example config/database.yml
bundle exec rake db:create
bundle exec rake db:schema:load
bundle exec rake cg:test:update_fixtures
bundle exec rake db:test:prepare
RAILS_ENV=test bundle exec rake db:fixtures:load
RAILS_ENV=test bundle exec rake ts:index ts:start
bundle exec rails test
bundle exec rails test extensions/pages
bundle exec rake cg:cleanup:all # test cleanup tasks
artifacts:
when: on_failure
paths:
- log/test.log
- tmp/*.log
- tmp/*.html
- tmp/*.png
test_stretch:
image: 0xacab.org:4567/riseuplabs/docker/crabgrass:stretch_amd64
stage: test

View File

@ -11,8 +11,7 @@ end
##
# Rails is the framework we use.
#gem 'rails', '~> 5.1.6'
gem 'rails', '~> 5.0.7'
gem 'rails', '~> 5.1.6'
# Security updates
# https://github.com/sparklemotion/nokogiri/issues/1785
@ -144,8 +143,7 @@ gem 'greencloth', require: 'greencloth',
# media upload post processing has it's own repo
# version is rather strict for now as api may still change.
gem 'crabgrass_media', '~> 0.3.0', require: 'media',
path: 'vendor/gems/crabgrass-media'
gem 'crabgrass_media', '~> 0.3.0', require: 'media'
##
## not required, but a really good idea

View File

@ -6,13 +6,6 @@ GIT
prototype-rails (4.1.3)
rails (>= 4.2)
PATH
remote: vendor/gems/crabgrass-media
specs:
crabgrass_media (0.3.0)
activesupport (~> 5.0)
mime-types (~> 3.1)
PATH
remote: vendor/gems/riseuplabs-greencloth-0.1
specs:
@ -24,89 +17,94 @@ GEM
specs:
RedCloth (4.3.2)
aasm (3.4.0)
actioncable (5.0.7.2)
actionpack (= 5.0.7.2)
nio4r (>= 1.2, < 3.0)
actioncable (5.1.7)
actionpack (= 5.1.7)
nio4r (~> 2.0)
websocket-driver (~> 0.6.1)
actionmailer (5.0.7.2)
actionpack (= 5.0.7.2)
actionview (= 5.0.7.2)
activejob (= 5.0.7.2)
actionmailer (5.1.7)
actionpack (= 5.1.7)
actionview (= 5.1.7)
activejob (= 5.1.7)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.0.7.2)
actionview (= 5.0.7.2)
activesupport (= 5.0.7.2)
actionpack (5.1.7)
actionview (= 5.1.7)
activesupport (= 5.1.7)
rack (~> 2.0)
rack-test (~> 0.6.3)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionpack-page_caching (1.1.0)
actionpack-page_caching (1.1.1)
actionpack (>= 4.0.0, < 6)
actionview (5.0.7.2)
activesupport (= 5.0.7.2)
actionview (5.1.7)
activesupport (= 5.1.7)
builder (~> 3.1)
erubis (~> 2.7.0)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.0.7.2)
activesupport (= 5.0.7.2)
activejob (5.1.7)
activesupport (= 5.1.7)
globalid (>= 0.3.6)
activemodel (5.0.7.2)
activesupport (= 5.0.7.2)
activerecord (5.0.7.2)
activemodel (= 5.0.7.2)
activesupport (= 5.0.7.2)
arel (~> 7.0)
activesupport (5.0.7.2)
activemodel (5.1.7)
activesupport (= 5.1.7)
activerecord (5.1.7)
activemodel (= 5.1.7)
activesupport (= 5.1.7)
arel (~> 8.0)
activesupport (5.1.7)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
acts-as-taggable-on (6.0.0)
activerecord (~> 5.0)
acts_as_list (0.9.1)
acts_as_list (0.9.19)
activerecord (>= 3.0)
addressable (2.5.0)
public_suffix (~> 2.0, >= 2.0.2)
arel (7.1.4)
bcrypt (3.1.11)
addressable (2.6.0)
public_suffix (>= 2.0.2, < 4.0)
arel (8.0.0)
bcrypt (3.1.12)
builder (3.2.3)
bundler-audit (0.6.1)
bundler (>= 1.2.0, < 3)
thor (~> 0.18)
byebug (9.0.6)
capybara (2.12.0)
byebug (11.0.1)
capybara (3.15.1)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (~> 1.2)
xpath (~> 3.2)
chronic (0.10.2)
cliver (0.3.2)
concurrent-ruby (1.1.5)
crabgrass_media (0.3.0)
activesupport (~> 5.0)
mime-types (~> 3.1)
crass (1.0.4)
daemons (1.2.4)
delayed_job (4.1.2)
activesupport (>= 3.0, < 5.1)
delayed_job_active_record (4.1.1)
activerecord (>= 3.0, < 5.1)
daemons (1.3.1)
delayed_job (4.1.5)
activesupport (>= 3.0, < 5.3)
delayed_job_active_record (4.1.3)
activerecord (>= 3.0, < 5.3)
delayed_job (>= 3.0, < 5)
docile (1.1.5)
docile (1.3.1)
erubi (1.8.0)
erubis (2.7.0)
execjs (2.7.0)
factory_bot (4.8.2)
activesupport (>= 3.0.0)
factory_bot_rails (4.8.2)
factory_bot (~> 4.8.2)
railties (>= 3.0.0)
factory_bot (5.0.2)
activesupport (>= 4.2.0)
factory_bot_rails (5.0.2)
factory_bot (~> 5.0.2)
railties (>= 4.2.0)
faker (1.0.1)
i18n (~> 0.4)
ffi (1.9.25)
ffi (1.10.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
gpgme (2.0.16)
gpgme (2.0.18)
mini_portile2 (~> 2.3)
haml (5.0.4)
temple (>= 0.8.0)
@ -122,14 +120,14 @@ GEM
haml (>= 4.0, < 6)
nokogiri (>= 1.6.0)
ruby_parser (~> 3.5)
http_accept_language (2.1.0)
http_accept_language (2.1.1)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
innertube (1.1.0)
joiner (0.3.4)
activerecord (>= 4.1.0)
json (1.8.6)
libv8 (3.16.14.17)
libv8 (3.16.14.19)
loofah (2.2.3)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
@ -140,9 +138,9 @@ GEM
mail (~> 2.5, >= 2.5.3)
method_source (0.9.2)
middleware (0.1.0)
mime-types (3.1)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mime-types-data (3.2019.0331)
mini_mime (1.0.1)
mini_portile2 (2.3.0)
minitest (5.10.3)
@ -152,27 +150,27 @@ GEM
mini_portile2 (~> 2.3.0)
phantomjs-binaries (2.1.1.1)
sys-uname (= 0.9.0)
poltergeist (1.13.0)
capybara (~> 2.1)
poltergeist (1.18.1)
capybara (>= 2.1, < 4)
cliver (~> 0.3.1)
websocket-driver (>= 0.2.0)
public_suffix (2.0.5)
public_suffix (3.0.3)
pundit (1.1.0)
activesupport (>= 3.0.0)
rack (2.0.7)
rack-test (0.6.3)
rack (>= 1.0)
rails (5.0.7.2)
actioncable (= 5.0.7.2)
actionmailer (= 5.0.7.2)
actionpack (= 5.0.7.2)
actionview (= 5.0.7.2)
activejob (= 5.0.7.2)
activemodel (= 5.0.7.2)
activerecord (= 5.0.7.2)
activesupport (= 5.0.7.2)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (5.1.7)
actioncable (= 5.1.7)
actionmailer (= 5.1.7)
actionpack (= 5.1.7)
actionview (= 5.1.7)
activejob (= 5.1.7)
activemodel (= 5.1.7)
activerecord (= 5.1.7)
activesupport (= 5.1.7)
bundler (>= 1.3.0)
railties (= 5.0.7.2)
railties (= 5.1.7)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.4)
actionpack (>= 5.0.1.x)
@ -183,34 +181,41 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.0.4)
loofah (~> 2.2, >= 2.2.2)
railties (5.0.7.2)
actionpack (= 5.0.7.2)
activesupport (= 5.0.7.2)
railties (5.1.7)
actionpack (= 5.1.7)
activesupport (= 5.1.7)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rake (10.5.0)
rdoc (4.3.0)
rb-fsevent (0.10.3)
rb-inotify (0.10.0)
ffi (~> 1.0)
rdoc (6.1.1)
record_tag_helper (1.0.0)
actionview (~> 5.x)
ref (2.0.0)
riddle (2.2.2)
regexp_parser (1.4.0)
riddle (2.3.2)
ruby_parser (3.13.1)
sexp_processor (~> 4.9)
rubyzip (1.2.2)
sass (3.4.23)
sdoc (0.4.2)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
sdoc (1.0.0)
rdoc (>= 5.0)
secure_headers (4.0.2)
useragent (>= 0.15.0)
sexp_processor (4.12.0)
simplecov (0.14.1)
docile (~> 1.1.0)
simplecov (0.16.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.1)
spring (2.0.1)
simplecov-html (0.10.2)
spring (2.0.2)
activesupport (>= 4.2)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
@ -219,7 +224,7 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.13)
sqlite3 (1.4.0)
sys-uname (0.9.0)
ffi (>= 1.0.0)
temple (0.8.1)
@ -236,26 +241,27 @@ GEM
thor (0.20.3)
thread_safe (0.3.6)
tilt (2.0.9)
ts-delayed-delta (2.0.2)
ts-delayed-delta (2.1.0)
activerecord (>= 2.0)
delayed_job
thinking-sphinx (>= 1.5.0)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (3.0.4)
uglifier (4.1.20)
execjs (>= 0.3.0, < 3)
useragent (0.16.9)
useragent (0.16.10)
utf8-cleaner (0.2.5)
activesupport
validates_email_format_of (1.6.3)
i18n
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
whenever (0.9.7)
websocket-extensions (0.1.3)
whenever (0.11.0)
chronic (>= 0.6.3)
will_paginate (3.1.5)
xpath (2.0.0)
nokogiri (~> 1.3)
will_paginate (3.1.7)
xpath (3.2.0)
nokogiri (~> 1.8)
zip-zip (0.3)
rubyzip (>= 1.0.0)
@ -272,7 +278,7 @@ DEPENDENCIES
bundler-audit
byebug
capybara
crabgrass_media (~> 0.3.0)!
crabgrass_media (~> 0.3.0)
daemons
delayed_job_active_record (~> 4.0)
factory_bot_rails
@ -293,7 +299,7 @@ DEPENDENCIES
poltergeist (~> 1.5)
prototype-rails!
pundit (~> 1.1)
rails (~> 5.0.7)
rails (~> 5.1.6)
rails-controller-testing
rake (~> 10.0)
record_tag_helper (~> 1.0)

View File

@ -39,8 +39,6 @@ class Page::CreateController < ApplicationController
redirect_to page_url(@page)
end
protected
# if the page controller is called by our custom DispatchController,
# objects which have already been loaded will be passed to the tool
# via this initialize method.
@ -51,6 +49,8 @@ class Page::CreateController < ApplicationController
@page = seed[:page] # the page object, if already fetched
end
protected
#
# before filters
#

View File

@ -80,7 +80,7 @@ class Asset < ApplicationRecord
# This is included here because Asset may take new attachment file data, but
# Asset::Version and Thumbnail don't need to.
include Asset::Upload
validates_presence_of :filename, unless: 'new_record?'
validates_presence_of :filename, unless: :new_record?
##
## ACCESS

View File

@ -1,4 +1,4 @@
class AddIndexToPageHistories < ActiveRecord::Migration
class AddIndexToPageHistories < ActiveRecord::Migration[4.2]
def change
add_index "page_histories", ["notification_digest_sent_at"]
end

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@ class GalleryImageControllerTest < ActionController::TestCase
def test_show_not_found
login_as :blue
assert @asset.id, 'image should not be nil'
xhr :get, :show, id: 111, page_id: @gallery.id
get :show, params: { id: 111, page_id: @gallery.id }, xhr: true
assert_not assigns(:showing)
assert_response 404
end

View File

@ -9,19 +9,6 @@ class TasksControllerTest < ActionController::TestCase
login_as @user
end
def test_sort
assert_equal 1, Task.find(1).position
assert_equal 2, Task.find(2).position
assert_equal 3, Task.find(3).position
post :sort, params: { page_id: @page.id, sort_list_pending: ["3", "2", "1"] }, xhr: true
assert_response :success
assert_equal 3, Task.find(1).position
assert_equal 2, Task.find(2).position
assert_equal 1, Task.find(3).position
end
def test_create_task
assert_difference '@page.tasks.count' do
post :create, params: { page_id: @page.id, task: { name: "new task", user_ids: ["5"], description: "new task description" } }, xhr: true

View File

@ -39,7 +39,7 @@ class Me::PostsControllerTest < ActionController::TestCase
post = Message.send from: me, to: you, body: 'test message'
login_as me
assert_difference 'Post.count', -1 do
delete :destroy, discussion_id: you.login, id: post
delete :destroy, params: { discussion_id: you.login, id: post }
assert_response :redirect
end
end

View File

@ -5,11 +5,12 @@ class Wiki::VersionsControllerTest < ActionController::TestCase
@user = FactoryBot.create(:user)
@group = FactoryBot.create(:group)
@group.add_user!(@user)
@wiki = @group.profiles.public.create_wiki body: 'test'
@wiki.body = @original_body = 'original wiki body'
@wiki = @group.profiles.public.create_wiki body: 'original wiki body'
@wiki.updated_at = 1.day.ago # force an older timestamp, so that
# changing the wiki will create a new version.
@wiki.save
@wiki.body = 'updated wiki body'
@wiki.save
@version = @wiki.versions.last
login_as @user
end

View File

@ -1,8 +0,0 @@
.bundle/
log/*.log
pkg/
test/dummy/db/*.sqlite3
test/dummy/db/*.sqlite3-journal
test/dummy/log/*.log
test/dummy/tmp/
test/dummy/.sass-cache

View File

@ -1,29 +0,0 @@
image: 0xacab.org:4567/riseuplabs/docker/crabgrass:stretch_amd64
cache:
paths:
- vendor/ruby
before_script:
- ruby -v
- which ruby
- bundle --version
- libreoffice --version
- bundle install -j $(nproc) --path vendor "${FLAGS[@]}"
test_stretch:
stage: test
script: |
bundle exec rake
artifacts:
when: on_failure
paths:
- log/*.log
bundle_audit:
stage: test
allow_failure: true
script: |
sudo gem install bundler-audit
bundle-audit update
bundle-audit check

View File

@ -1,20 +0,0 @@
language: ruby
sudo: :required
rvm:
- 1.9.3
- 2.1.5
matrix:
fast_finish: true
addons:
apt:
packages:
- graphicsmagick
- inkscape
before_install:
- sudo apt-get install libreoffice-writer
- bundle --version
- libreoffice --version
notifications:
email: false
after_script:
- "cat log/*.log" # printing all logs to investigate issues on CI

View File

@ -1,34 +0,0 @@
<a name="0.2.0"></a>
### 0.2.0 (2017-02-23)
#### Upgrade
* require rails 4.2
#### Bug Fixes
* fix all warnings from gem build ([9add68a](/../commit/9add68a))
* warnings during test run ([245564f](/../commit/245564f))
* duplicate keys in mime type hash ([131cb9b](/../commit/131cb9b))
<a name="0.1.1"></a>
### 0.1.1 (2017-02-24)
#### Bug Fixes
* prevent raising ENOENT on failure ([dfb0baf](/../commit/dfb0baf))
<a name="0.1.0"></a>
### 0.1.0 (2017-02-24)
#### Features
* use new class names for crabgrass 0.7 ([909295f](/../commit/909295f))

View File

@ -1,7 +0,0 @@
source 'https://rubygems.org'
# Declare your gem's dependencies in media.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
# development dependencies will be added by default to the :development group.
gemspec

View File

@ -1,39 +0,0 @@
PATH
remote: .
specs:
crabgrass_media (0.3.0)
activesupport (~> 5.0)
mime-types (~> 3.1)
GEM
remote: https://rubygems.org/
specs:
activesupport (5.1.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
minitest (~> 5.1)
tzinfo (~> 1.1)
concurrent-ruby (1.0.5)
conventional-changelog (1.2.2)
i18n (0.9.1)
concurrent-ruby (~> 1.0)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
minitest (5.11.1)
rake (12.0.0)
thread_safe (0.3.6)
tzinfo (1.2.4)
thread_safe (~> 0.1)
PLATFORMS
ruby
DEPENDENCIES
conventional-changelog (~> 1.2)
crabgrass_media!
minitest (~> 5.10)
rake (~> 12.0)
BUNDLED WITH
1.16.0

View File

@ -1,22 +0,0 @@
Crabgrass::Media is the Media engine of Crabgrass.
Crabgrass is a web application designed for activist groups to be better able to collaborate online. Mostly, it is a glorified wiki with fine-grain control over access rights.
Crabgrass::Media is a rails engine to do media transformations.
You can add new media transformations by subclassing Transmogrifier.
Example usage:
```ruby
transmog = Media.transmogrifier(:input_file => 'myfile.odt', :output_file => 'myfile.jpg')
status = transmog.run do |progress|
puts progress
end
```
Tests require the 'file' utility to be installed to determine the file
type of the created files.
Crabgrass and Crabgrass::Media are based on Ruby on Rails and MySQL.
They are released under the AGPL license, version 3.

View File

@ -1,34 +0,0 @@
begin
require 'bundler/setup'
rescue LoadError
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
end
require 'rdoc/task'
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'Media'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('lib/**/*.rb')
end
Bundler::GemHelper.install_tasks
require 'rake/testtask'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = false
end
task default: :test

View File

@ -1,9 +0,0 @@
unless defined?(Media::TMP_PATH)
if defined?(Rails)
Media::TMP_PATH = File.join(Rails.root, 'tmp', 'media')
else
Media::TMP_PATH = File.join('', 'tmp', 'media')
end
end
FileUtils.mkdir_p(Media::TempFile.tempfile_path) unless File.exist?(Media::TempFile.tempfile_path)

View File

@ -1,33 +0,0 @@
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require "media/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "crabgrass_media"
s.version = Media::VERSION
s.authors = ["Azul"]
s.email = ["azul@riseup.net"]
s.homepage = "https://github.com/riseuplabs/crabgrass-media"
s.summary = "Media processing for the Crabgrass social wiki"
s.description = <<-EOD
Crabgrass::Media is the Media engine of Crabgrass.
Crabgrass is a web application designed for activist groups to be better able to collaborate online. Mostly, it is a glorified wiki with fine-grain control over access rights.
Crabgrass::Media is a rails engine to do media transformations.
EOD
s.license = "AGPL-3.0"
s.files = Dir["{config,lib}/**/*", "Rakefile", "README.md"]
s.test_files = Dir["test/**/*"]
s.add_dependency 'activesupport', '~> 5.0'
s.add_dependency 'mime-types', '~> 3.1'
s.add_development_dependency 'rake', '~> 12.0'
s.add_development_dependency 'minitest', ' ~> 5.10'
s.add_development_dependency 'conventional-changelog', '~> 1.2'
end

View File

@ -1,62 +0,0 @@
require 'media/mime_type'
require 'media/temp_file'
require 'media/transmogrifier'
require 'media/transmogrifiers/graphicsmagick'
require 'media/transmogrifiers/inkscape'
require 'media/transmogrifiers/libremagick'
require 'media/transmogrifiers/libreoffice'
module Media
#
# creates a new instance of transmogrifier suitable for turning
# input into output.
#
def self.transmogrifier(options)
if options[:input_type]
input_type = Media::MimeType.simple options[:input_type]
elsif options[:input_file]
input_type = Media::MimeType.mime_type_from_extension options[:input_file]
else
raise ArgumentError.new
end
if options[:output_type]
output_type = Media::MimeType.simple options[:output_type]
elsif options[:output_file]
output_type = Media::MimeType.mime_type_from_extension options[:output_file]
else
raise ArgumentError.new
end
unless input_type and output_type
raise ArgumentError.new("Both input and output types are required (given %s -> %s)." % [input_type||'nil', output_type||'nil'])
end
transmog_class = Transmogrifier.find_class(input_type, output_type)
if transmog_class
transmog_class.new(options)
end
end
def self.may_produce?(src_type, dst_type)
Transmogrifier.find_class(src_type, dst_type)
end
#
# special graphicsmagick hooks.
# we use graphicsmagick in order to parse the dimensions of image files.
#
def self.has_dimensions?(mime_type)
Media::GraphicsMagickTransmogrifier.available? &&
Media::GraphicsMagickTransmogrifier.converts_from?(mime_type)
end
def self.dimensions(filepath)
return unless Media::GraphicsMagickTransmogrifier.available?
Media::GraphicsMagickTransmogrifier.dimensions filepath
end
end

View File

@ -1,257 +0,0 @@
require 'mime/types'
module Media
module MimeType
def self.mime_group(mime_type)
mime_type.sub(/\/.*$/,'/') if mime_type # remove everything after /
end
def self.simple(mime_type)
mime_type.to_s.sub(/\/x\-/,'/') if mime_type # remove x-
end
def self.lookup(mime_type,field)
(MIME_TYPES[simple(mime_type)]||[])[field]
end
# def self.group_from_mime_type(mime_type)
# lookup(mime_type,GROUP) || lookup(mime_group(mime_type),GROUP)
# end
def self.icon_for(mtype)
lookup(mtype,ICON) || lookup(mime_group(mtype),ICON) || lookup('default',ICON)
end
def self.asset_class_from_mime_type(mime_type)
asset_symbol_from_mime_type(mime_type).to_s.classify
end
def self.asset_symbol_from_mime_type(mime_type)
lookup(mime_type,ASSET_CLASS) || lookup(mime_group(mime_type),ASSET_CLASS) || lookup('default',ASSET_CLASS)
end
def self.extension_from_mime_type(mime_type)
lookup(mime_type,EXT)
end
def self.mime_type_from_extension(ext)
ext = ext.to_s
ext = File.extname(ext).gsub('.','') if ext =~ /\./
mimetype = EXTENSIONS[ext]
if defined?(MIME::Types)
unless MIME::Types.type_for('.'+ext).empty?
mimetype ||= MIME::Types.type_for('.'+ext).first.content_type
end
end
mimetype ||= 'application/octet-stream'
return mimetype
end
#
# perhaps use http://code.google.com/p/mimetype-fu/
# for all this?
def self.type_for(filename)
self.mime_type_from_extension(filename)
# todo: add type_from_file_command if ext doesn't pan out.
end
#def type_from_file_command(filename)
# # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
# type = (filename.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
# mime_type = (Paperclip.run("file", "-b --mime-type :file", :file => filename).split(':').last.strip rescue "application/x-#{type}")
# mime_type = "application/x-#{type}" if mime_type.match(/\(.*?\)/)
# mime_type
#end
def self.description_from_mime_type(mime_type)
lookup(mime_type,DESCRIPTION) || lookup(mime_group(mime_type),DESCRIPTION) || lookup('default',DESCRIPTION)
end
def self.compatible_types?(type1, type2)
(TYPE_ALIASES[type1] || []).include?(type2)
end
EXT = 0; ICON = 1; ASSET_CLASS = 2; DESCRIPTION = 3;
MIME_TYPES = {
# mime_type => [file_extension, icon, asset_class, description]
'default' => [nil,'default',:asset,'Unknown'],
'text/' => [:txt,:html,'asset/text', 'Text'],
'text/plain' => [:txt,:html,'asset/text', 'Text'],
'text/html' => [:html,:html,'asset/text', 'Webpage'],
'application/rtf' => [:rtf,:rtf,'asset/text', 'Rich Text'],
'text/rtf' => [:rtf,:rtf,'asset/text', 'Rich Text'],
'text/sgml' => [:sgml,:xml,nil,'XML'],
'text/xml' => [:xml,:xml,nil,'XML'],
'text/csv' => [:csv,:spreadsheet,'asset/doc', 'Comma Separated Values'],
'text/comma-separated-values' => [:csv,:spreadsheet,'asset/doc', 'Comma Separated Values'],
'application/pdf' => [:pdf,:pdf,'asset/image', 'Portable Document Format'],
'application/bzpdf' => [:pdf,:pdf,'asset/image', 'Portable Document Format'],
'application/gzpdf' => [:pdf,:pdf,'asset/image', 'Portable Document Format'],
'application/postscript' => [:ps,:pdf,'asset/image','Postscript'],
'text/spreadsheet' => [:txt,:spreadsheet,'asset/doc','Spreadsheet'],
'application/gnumeric' => [:gnumeric,:spreadsheet,'asset/doc','Gnumeric'],
'application/kspread' => [:kspread,:spreadsheet,'asset/doc','KSpread'],
'application/scribus' => [:scribus,:doc,nil,'Scribus'],
'application/abiword' => [:abw,:doc,'asset/doc','Abiword'],
'application/kword' => [:kwd,:doc,'asset/doc','KWord'],
'application/msword' => [:doc,:msword,'asset/text','MS Word'],
'application/mswrite' => [:doc,:msword,'asset/text','MS Write'],
'application/powerpoint' => [:ppt,:mspowerpoint,'asset/doc','MS Powerpoint'],
'application/excel' => [:xls,:msexcel,'asset/spreadsheet','MS Excel'],
'application/access' => [nil, :msaccess, 'asset/doc','MS Access'],
'application/vnd.ms-msword' => [:doc,:msword,'asset/text','MS Word'],
'application/vnd.ms-mswrite' => [:doc,:msword,'asset/text','MS Write'],
'application/vnd.ms-powerpoint' => [:ppt,:mspowerpoint,'asset/doc','MS Powerpoint'],
'application/vnd.ms-excel' => [:xls,:msexcel,'asset/spreadsheet','MS Excel'],
'application/vnd.ms-access' => [nil, :msaccess, 'asset/doc','MS Access'],
'application/msword-template' => [:dot,:msword,'asset/text','MS Word Template'],
'application/excel-template' => [:xlt,:msexcel,'asset/spreadsheet','MS Excel Template'],
'application/powerpoint-template' => [:pot,:mspowerpoint,'asset/doc','MS Powerpoint Template'],
# 'application/vnd.openxmlformats-officedocument.presentationml.presentation' =>
# [:pptx, :mspowerpoint,'asset/doc','MS Powerpoint'],
'application/vnd.openxmlformats-officedocument.presentationml.presentation' =>
[:pptm, :mspowerpoint,'asset/doc','MS Powerpoint'],
'application/vnd.openxmlformats-officedocument.presentationml.template' =>
[:potx,:mspowerpoint,'asset/doc','MS Powerpoint Template'],
# 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' =>
# [:docm,:msword,'asset/text','MS Word'],
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' =>
[:docx,:msword,'asset/text','MS Word'],
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' =>
[:dotx,:msword,'asset/text','MS Word Template'],
# 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' =>
# [:xlsm,:msexcel,'asset/spreadsheet','MS Excel'],
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' =>
[:xlsx,:msexcel,'asset/spreadsheet','MS Excel'],
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' =>
[:xltx,:msexcel,'asset/spreadsheet','MS Excel Template'],
'application/executable' => [nil,:binary,nil,'Program'],
'application/ms-dos-executable' => [nil,:binary,nil,'Program'],
'application/octet-stream' => [nil,:binary,nil],
'application/shellscript' => [:sh,:shell,nil,'Script'],
'application/ruby' => [:rb,:ruby,nil,'Script'],
'application/vnd.oasis.opendocument.spreadsheet' =>
[:ods,:oo_spreadsheet,'asset/spreadsheet', 'OpenDocument Spreadsheet'],
'application/vnd.oasis.opendocument.formula' =>
[nil,:oo_spreadsheet,'asset/spreadsheet', 'OpenDocument Formula'],
'application/vnd.oasis.opendocument.chart' =>
[nil,:oo_spreadsheet,'asset/spreadsheet', 'OpenDocument Chart'],
'application/vnd.oasis.opendocument.image' =>
[nil,:oo_graphics, 'asset/doc', 'OpenDocument Image'],
'application/vnd.oasis.opendocument.graphics' =>
[:odg,:oo_graphics, 'asset/doc', 'OpenDocument Graphics'],
'application/vnd.oasis.opendocument.presentation' =>
[:odp,:oo_presentation,'asset/doc', 'OpenDocument Presentation'],
'application/vnd.oasis.opendocument.database' =>
[:odf,:oo_database,'asset/doc', 'OpenDocument Database'],
'application/vnd.oasis.opendocument.text-web' =>
[:html,:oo_html,'asset/doc', 'OpenDocument Webpage'],
'application/vnd.oasis.opendocument.text' =>
[:odt,:oo_document,'asset/doc', 'OpenDocument Text'],
'application/vnd.oasis.opendocument.text-master' =>
[:odm,:oo_document,'asset/doc', 'OpenDocument Master'],
'application/vnd.oasis.opendocument.presentation-template' =>
[:otp,:oo_presentation,'asset/doc', 'OpenDocument Presentation'],
'application/vnd.oasis.opendocument.graphics-template' =>
[:otg,:oo_graphics,'asset/doc', 'OpenDocument Graphics'],
'application/vnd.oasis.opendocument.spreadsheet-template' =>
[:ots,:oo_spreadsheet,'asset/spreadsheet', 'OpenDocument Spreadsheet'],
'application/vnd.oasis.opendocument.text-template' =>
[:ott,:oo_document,'asset/doc', 'OpenDocument Text'],
'packages/' => [nil,:archive,nil,'Archive'],
'multipart/zip' => [:zip,:archive,nil,'Archive'],
'multipart/gzip' => [:gzip,:archive,nil,'Archive'],
'multipart/tar' => [:tar,:archive,nil,'Archive'],
'application/zip' => [:gzip,:archive,nil,'Archive'],
'application/gzip' => [:gzip,:archive,nil,'Archive'],
'application/rar' => [:rar,:archive,nil,'Archive'],
'application/deb' => [:deb,:archive,nil,'Archive'],
'application/tar' => [:tar,:archive,nil,'Archive'],
'application/stuffit' => [:sit,:archive,nil,'Archive'],
'application/compress' => [nil,:archive,nil,'Archive'],
'application/zip-compressed' => [:zip,:archive,nil,'Archive'],
'video/' => [nil,:video,nil,'Video'],
'audio/' => [nil,:audio,'asset/audio','Audio'],
'image/' => [nil,:image,'asset/image','Image'],
'image/jpeg' => [:jpg,:image,'asset/image', 'JPEG Image'],
'image/jpg' => [:jpg,:image,'asset/image', 'JPEG Image'],
'image/png' => [:png,:image,'asset/png', 'PNG Image'],
'image/gif' => [:png,:image,'asset/gif', 'GIF Image'],
'image/svg+xml' => [:svg,:vector,'asset/svg','Vector Graphic'],
'image/svg+xml-compressed' => [:svg,:vector,'asset/svg','Vector Graphic'],
'application/illustrator' => [:ai,:vector,'asset/image','Vector Graphic'],
'image/bzeps' => [:bzeps,:vector,'asset/image','Vector Graphic'],
'image/eps' => [:eps,:vector,'asset/image','Vector Graphic'],
'image/gzeps' => [:gzeps,:vector,'asset/image','Vector Graphic'],
'application/pgp-encrypted' => [nil,:lock,nil,'Crypt'],
'application/pgp-signature' => [nil,:lock,nil,'Crypt'],
'application/pgp-keys' => [nil,:lock,nil,'Crypt']
}.freeze
#
# This extension mapping is used to force certain mime types.
# Usually, firefox does pretty good at reporting the correct mime-type,
# but IE always fails (firefox fails on ogg). So, we use the MIME::Types
# gem to try to get the correct mime from the extension. Sometimes, however,
# even this doesn't work. This able will force certain types when
# MIME::Types fails or is ambiguous
#
EXTENSIONS = {
'jpg' => 'image/jpeg',
'png' => 'image/png',
'txt' => 'text/plain',
'flv' => 'video/flv',
'ogg' => 'audio/ogg',
'oga' => 'audio/ogg',
'ogv' => 'video/ogg',
'pdf' => 'application/pdf',
'doc' => 'application/msword',
'xsl' => 'application/excel',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'pptm' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'docm' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'xlsm' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'odt' => 'application/vnd.oasis.opendocument.text',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'svg' => 'image/svg+xml',
'mod' => 'video/mpeg',
}.freeze
#
# some types can have multiple names
#
TYPE_ALIASES = {
'image/jpg' => ['image/jpeg'],
'image/jpeg' => ['image/jpg']
}
end
end

View File

@ -1,152 +0,0 @@
require 'tempfile'
require 'fileutils'
require 'pathname'
#
# media processing requires a different type of tempfile... because we use command
# line tools to process our temp files, these files can't be open and closed by ruby.
#
# instead, Media::TempFile is used to generate closed files from binary data (for files
# to be fed to command line tools), or to generate empty tmp files (for output filenames
# to be fed to command line tools).
#
# We use the Tempfile class for generating these files, but then we always close them
# right away. By doing this, we ensure that the temp file will eventually get removed
# when the Tempfile gets garbage collected.
#
module Media
class TempFile
def self.tempfile_path
::Media::TMP_PATH
end
##
## INSTANCE METHODS
##
public
#
# data may be one of:
#
# - FileUpload object: like the kind returned in multibyte encoded file upload forms.
# - Pathname object: then load data from the file pointed to by the pathname.
# - IO object: read the contents of the io object, copy to tmp file.
# - otherwise, dump the contents of the data to the tmp file.
#
# if data is empty, we generate an empty one.
#
def initialize(data, content_type=nil)
if data.nil?
@tmpfile = TempFile.create_from_content_type(content_type)
elsif data.respond_to?(:path)
# we are dealing with an uploaded file object
@tmpfile = TempFile.create_from_file(data.path, content_type, {mode: :move})
elsif data.is_a?(StringIO)
data.rewind
@tmpfile = TempFile.create_from_data(data.read, content_type)
elsif data.instance_of?(Pathname)
@tmpfile = TempFile.create_from_file(data.to_s, content_type)
else
@tmpfile = TempFile.create_from_data(data, content_type)
end
end
#
# like initialize, but if given a block, then it yields the TempFile
# and also unlinks the file at the end of the block.
#
def self.open(data, content_type=nil)
tmp = TempFile.new(data, content_type)
if block_given?
begin
yield tmp
ensure
tmp.clear
end
nil
else
tmp
end
end
def clear
# this is not really needed, because the tmp files are deleted as soon as
# @tmpfile gets garbage collected.
# @tmpfile.unlink
end
def any?
@tmpfile.any?
end
def path
@tmpfile.path
end
def to_s
@tmpfile.path
end
##
## CLASS METHODS
##
private
#
# creates a tempfile filled with the given binary data
#
def self.create_from_data(data, content_type=nil)
tf = new_for_content_type(content_type)
tf.binmode
tf.write(data)
tf.close
tf
end
#
# create an empty temp file with an extension to match the content_type
#
def self.create_from_content_type(content_type)
tf = new_for_content_type(content_type)
tf.close
tf
end
#
# create a tmp file that is a copy of another file.
#
def self.create_from_file(filepath, content_type, options = {})
tf = new_for_content_type(content_type)
tf.close
if options[:mode] == :move
FileUtils.mv filepath, tf.path
else
FileUtils.cp filepath, tf.path
end
tf
end
def self.new_for_content_type(content_type)
Tempfile.new content_type_basename(content_type),
tempfile_path,
mode: 022
end
#
# create a filename with a file extension from the content_type
#
def self.content_type_basename(content_type)
if content_type
extension = Media::MimeType.extension_from_mime_type(content_type) || :bin
['media_temp_file', ".#{extension}"]
else
'media_temp_file'
end
end
end
end

View File

@ -1,331 +0,0 @@
#
# A class to transmogrify a media asset from one form to another.
#
# All such media transformations are handled by instances of this class.
#
require 'fileutils'
require 'active_support/core_ext/module/delegation.rb'
require 'active_support/core_ext/module/attribute_accessors.rb'
require 'logger'
module Media
class Transmogrifier
# singleton logger, only set via class, access via class and instance
mattr_accessor :logger, instance_writer: false do
Logger.new(STDERR)
end
mattr_accessor :verbose, instance_writer: false
class << self
delegate :debug, :info, :warning, :error, to: :logger
end
delegate :debug, :info, :warning, :error, to: :logger
attr_accessor :input
attr_accessor :input_file
attr_accessor :input_type
attr_accessor :output # maybe some day we return raw output via url?
attr_accessor :output_type # desired mime type of the output
attr_accessor :output_file # desired file location of the output
attr_accessor :options
attr_accessor :command_output # output of last command run
#
# takes a hash of options, some of which are required:
#
# - :input_file or (:input and :input_type)
# - :output_file or :output_type
#
def initialize(options=nil)
options = options.dup
self.input = options.delete(:input)
self.input_file = options.delete(:input_file)
self.input_type = options.delete(:input_type)
self.output_file = options.delete(:output_file)
self.output_type = options.delete(:output_type)
self.options = options
if input and input_type.nil?
raise ArgumentError.new('input_type required if input specified')
elsif input and input_file.nil?
self.input_file = Media::TempFile.new(input, input_type)
elsif input and input_file
raise ArgumentError.new('cannot have both input and input_file')
elsif input_file and input_type.nil?
self.input_type = Media::MimeType.mime_type_from_extension(input_file)
elsif input.nil? and input_file.nil?
raise ArgumentError.new('input or input_file is required')
end
if output_file.nil? and output_type.nil?
raise ArgumentError.new('output_file or output_type is required')
elsif output_file.nil?
self.output_file = Media::TempFile.new(nil, output_type)
elsif output_type.nil?
self.output_type = Media::MimeType.mime_type_from_extension(output_file)
end
debug self.class.name + " converting" +
" #{input_file } ( #{input_type} ) to" +
" #{output_file} ( #{output_type} )"
set_temporary_outfile
end
def self.inherited(base)
add base
end
##
## CLASS METHODS
##
def self.list(); @@list ||= {}; end
# maps mine type to an array of transmogrifiers that
# take that type as an imput
def self.input_map; @@input_map ||= Hash.new([]); end
# maps mine type to an array of transmogrifiers that
# produce that type as an output
def self.output_map; @@output_map ||= Hash.new([]); end
def self.add(trans)
self.list[trans.name] ||= trans
end
#
# returns transmogrifier class, if any, that can tranform input_type
# into output_type
#
def self.find_class(input_type, output_type)
transmog = list.values.
select{|tm| tm.converts_from?(input_type)}.
select{|tm| tm.converts_to?(output_type)}.
select{|tm| tm.available?}.
first
return transmog if transmog
logger.error 'could not find a transmogrifier for "%s" -> "%s"' %
[input_type, output_type]
return nil
end
def self.converts_from?(input_type)
input_types.include? input_type
end
def self.converts_to?(output_type)
output_types.include? output_type
end
#
#def self.simple_type(mime_type)
# mime_type.gsub(/\/x\-/,'/') if mime_type
#end
##
## Helpers
##
#
# runs a shell command, passing each line that is output, as it is output
# to the block.
#
# returns
# * the status of the command, as one of the following symbols:
# :success, :failure, :not_found, :error
# * the output to STDOUT and STDERR of the command
#
def self.run_command(*args)
# run the command
cmdstr = command_string(*args)
output = ""
before = Time.now
IO.popen(cmdstr + ' 2>&1', 'r') do |pipe|
while line = pipe.gets
if block_given?
yield(line)
end
output << line << "\n"
end
end
took = Time.now - before
# set the status
status = case $?.exitstatus
when 0 then :success
when 1 then :failure
when 127 then :not_found
else :error
end
if status == :success
log_command cmdstr
debug "took #{took} seconds."
debug output
else
msg = ' exited with "%s"' % $?.exitstatus
error cmdstr
error msg
error output if !output.empty?
yield(msg) if block_given?
end
return status, output
end
def run_command(*args)
status, self.command_output = self.class.run_command(*args)
# restore the original output_file name
unless restore_temporary_outfile
msg = 'could not restore temporary outfile'
error msg
yield(msg) if block_given?
status = :failure
end
return status
end
def self.command_available?(command)
command and
File.file?(command) and
File.executable?(command)
end
##
## PROTECTED
##
protected
def self.log_command(command)
debug "COMMAND " + command
end
#
# returns a filename with the same base but a new extension
#
def replace_extension(filename, new_extension)
old_extension = (File.extname(filename) || '').to_s
new_extension = new_extension.to_s
if !old_extension.empty?
base = File.basename(filename, old_extension)
else
base = filename
end
if new_extension !~ /^\./
new_extension = "." + new_extension
end
if base =~ /\.$/
new_extension = new_extension.chomp
end
"#{base}#{new_extension}"
end
def extension(mime_type)
Media::MimeType.extension_from_mime_type(mime_type)
end
#
# usage:
#
# replace_file :from => filea, :to => fileb
#
def replace_file(args={})
from = args[:from].to_s
to = args[:to].to_s
raise ArgumentError if from.empty? || to.empty?
if File.exist?(from)
if File.exist?(to)
FileUtils.rm(to)
end
FileUtils.mv(from, to)
end
end
##
## PRIVATE
##
private
def self.command_string(*args)
args.collect {|arg| shell_escape(arg.to_s)}.join(' ')
end
def self.shell_escape(str)
if str.empty?
"''"
elsif str =~ %r{\A[0-9A-Za-z+_-]+\z}
str
else
result = ''
str.scan(/('+)|[^']+/) do
if $1
result << %q{\'} * $1.length
else
result << %Q{'#{$&}'}
end
end
result
end
end
#
# returns true if the file as a suffix that matches the mime_type
#
def compatible_extension?(file, type)
file = file.to_s
ext = Media::MimeType.extension_from_mime_type(type)
if ext.nil?
return true
# ^^ if there is no defined extension for this type, then
# whatever the file has is fine
else
file_ext_type = Media::MimeType.mime_type_from_extension(file)
return Media::MimeType.compatible_types?(type, file_ext_type)
end
end
#
# ensure that the output_file has the correct suffix
# by setting a temporary one if the current one is not good.
#
def set_temporary_outfile
@temporary_outfile = false
if !compatible_extension?(output_file, output_type)
@temporary_outfile = true
@outfile_to_return = output_file
self.output_file = Media::TempFile.new(nil, output_type)
end
end
#
# moves the current output_file to match the filename we are
# supposed to return (which is stored in @outfile_to_return
# by set_temporary_outfile)
#
def restore_temporary_outfile
if @temporary_outfile
debug "moving output from #{output_file} to #{@outfile_to_return}"
replace_file from: output_file, to: @outfile_to_return
self.output_file = @outfile_to_return
end
return true
end
end
end

View File

@ -1,151 +0,0 @@
module Media
#
# transform image formats using the graphicsmagick command line executable "gm"
# requires "apt-get install graphicsmagick"
#
unless defined?(GRAPHICSMAGICK_COMMAND)
GRAPHICSMAGICK_COMMAND = `which gm`.chomp
end
#
# this is a little bit brittle, but I am not sure how else to do it.
#
unless defined?(GRAPHICSMAGICK_VERSION)
version = `#{GRAPHICSMAGICK_COMMAND} -version | head -1`.strip.sub(/GraphicsMagick ([0-9]+\.[0-9]+\.[0-9]+).*/,'\1').split('.')
GRAPHICSMAGICK_VERSION = [version[0].to_i, version[1].to_i, version[2].to_i]
end
class GraphicsMagickTransmogrifier < Media::Transmogrifier
def self.input_types
%w( image/jpeg image/pjpeg image/gif
image/png image/x-png image/jpg image/tiff )
# application/pdf application/bzpdf application/gzpdf
# application/postscript application/xpdf
end
def self.output_types
%w( application/pdf image/jpeg image/pjpeg
image/gif image/png image/jpg image/tiff )
end
def self.available?
command_available?(GRAPHICSMAGICK_COMMAND)
end
#
# gm has an option -monitor that will spit out the progress.
# this could be interesting. we would need to use getc instead of gets
# on the pipe, since the progress is updated on a single line.
#
def run(&block)
if !File.exist?(input_file.to_s) ||
( File.size(input_file.to_s) == 0 )
debug "Could not find input file: #{input_file}"
return :not_fould
end
# try converting first page only
status = convert(input_file.to_s + '[0]', &block)
# retry with full file if result was empty
if File.exist?(output_file.to_s) && File.size(output_file.to_s) == 0
# reset filenames to the state before run
set_temporary_outfile
status = convert(&block)
end
FileUtils.chmod 0644, output_file.to_s if File.exist? output_file.to_s
return status
end
def convert(input = input_file.to_s, &block)
# +profile '*' will remove all the image profiles, which will save
# space (sometimes) and are not useful for thumbnails
arguments = [self.class.gm_command, 'convert', '+profile', "*"]
if options[:size]
# handle multiple size options, if it is an array.
sizes = options[:size].is_a?(Array) ? options[:size] : [options[:size]]
sizes.each do |size|
if version_less_than?(1,3,6)
size = size.sub('^','!')
end
arguments << '-geometry' << size
end
end
if options[:background]
# http://superuser.com/questions/213336/using-graphicsmagick-or-imagemagick-how-do-i-replace-transparency-with-a-fill-c
arguments << '-background' << options[:background] << '-extent' << '0x0'
end
if options[:crop]
# we add '+0+0' because we don't want tiles, just a single image
arguments << '-crop' << options[:crop]+'+0+0'
end
arguments << input << output_file
run_command(*arguments, &block)
end
# try to detect the dimensions of the first page.
# fallback to detecting dimensions of all pages.
def self.dimensions(filename)
return unless available?
run_dimensions(filename.to_s + '[0]') ||
run_dimensions(filename.to_s)
end
#
# returns the average color of an image, as represented by an array of red, green, blue values, integers
# in the range 0..255
#
# note: it is important that the geometry is "1x1!" ... without the ! this function might die a fiery death.
#
def self.average_color(filename)
if available?
args = [gm_command, 'convert', '-resize', '1x1!', filename, 'text:-']
color = nil
status, color = run_command(*args)
if status == :success
match = color.match(/^0,0: \(\s*(?<red>\d+),\s*(?<green>\d+),\s*(?<blue>\d+)\)/)
if match
return [match['red'].to_i, match['green'].to_i, match['blue'].to_i]
end
end
end
#if something goes wrong, assume white:
return [256,256,256]
end
protected
def self.run_dimensions(filename)
args = [gm_command, 'identify', '-format', '%m %w %h', filename]
status, dimensions = run_command(*args)
if status == :success
_type, width, height = dimensions.split(/\s/)
return [width,height]
end
end
# this override is just used for test, at the moment.
def self.gm_command
GRAPHICSMAGICK_COMMAND
end
def version_less_than?(major,minor,tiny)
installed_major, installed_minor, installed_tiny = GRAPHICSMAGICK_VERSION
if installed_major < major
true
elsif (installed_major == major)
if (installed_minor < minor)
true
elsif (installed_minor == minor) && (installed_tiny < tiny)
true
else
false
end
else
false
end
end
end
end

View File

@ -1,43 +0,0 @@
module Media
unless defined?(INKSCAPE_COMMAND)
INKSCAPE_COMMAND = `which inkscape`.chomp
end
class InkscapeTransmogrifier < Media::Transmogrifier
def self.magick
Media::Transmogrifier.list["Media::GraphicsMagickTransmogrifier"]
end
def self.output_types
magick.output_types
end
def self.input_types
%w(image/svg+xml image/svg+xml-compressed application/illustrator image/bzeps image/eps image/gzeps)
end
def self.available?
command_available?(INKSCAPE_COMMAND) and magick and magick.available?
end
def run(&block)
if output_type == 'image/png' and options.empty?
arguments = [INKSCAPE_COMMAND, '--without-gui', '--export-area-drawing', '--export-area-snap', input_file, '--export-png', output_file]
run_command(*arguments, &block)
else
png_output_file = Media::TempFile.new(nil, "image/png")
arguments = [INKSCAPE_COMMAND, '--without-gui', '--export-area-drawing', '--export-area-snap', input_file, '--export-png', png_output_file]
status = run_command(*arguments, &block)
return status if status != :success
magick_transmog = self.class.magick.new(
options.merge({
input_file: png_output_file, input_type: "image/png",
output_file: output_file, output_type: output_type
})
)
magick_transmog.run(&block)
end
end
end
end

View File

@ -1,62 +0,0 @@
module Media
#
# uses libreoffice and graphicsmagick transmogrifiers to convert from office documents to image documents,
# by way of PDF.
#
class LibreMagickTransmogrifier < Media::Transmogrifier
def self.libre
Media::Transmogrifier.list["Media::LibreOfficeTransmogrifier"]
end
def self.magick
Media::Transmogrifier.list["Media::GraphicsMagickTransmogrifier"]
end
def self.input_types
libre.input_types
end
def self.output_types
# we don't want to use this for pdf, since libreoffice by itself can generate pdf
magick.output_types - ['application/pdf']
end
def self.available?
libre &&
magick &&
libre.available? &&
magick.available? &&
ghostscript_available?
end
def self.ghostscript_available?
cmd = `which ghostscript`.chomp
!cmd.empty? && command_available?(cmd)
end
#
# run libreoffice and then graphicsmagick in succession.
#
# all the options are passed to graphicsmagic, and none to libreoffice,
# because they are probably not for libreoffice (like crop or resize).
#
def run(&block)
pdf_output_file = Media::TempFile.new(nil, "application/pdf")
libre_transmog = self.class.libre.new(
input_file: input_file, input_type: input_type,
output_file: pdf_output_file, output_type: "application/pdf")
status = libre_transmog.run(&block)
return status if status != :success
magick_transmog = self.class.magick.new(
options.merge({
input_file: pdf_output_file, input_type: "application/pdf",
output_file: output_file, output_type: output_type
})
)
magick_transmog.run(&block)
end
end
end

View File

@ -1,96 +0,0 @@
module Media
#
# This used to work using a hacky python script that connected to a running openoffice
# daemon. Now, libreoffice has a command line option to convert, so we are just going to
# use that because it is much easier.
#
# So, the daemon stuff is commented out. Maybe some day it will be useful if doing high
# volume document processing.
#
require 'tmpdir'
unless defined?(LIBREOFFICE_COMMAND)
cmd = `which libreoffice`.chomp
if cmd.chars.any?
LIBREOFFICE_COMMAND = cmd
else
LIBREOFFICE_COMMAND = false
end
end
class LibreOfficeTransmogrifier < Media::Transmogrifier
def self.input_types
%w(
text/plain text/html text/richtext application/rtf
text/csv text/comma-separated-values
application/msword application/mswrite application/powerpoint
application/excel application/access application/vnd.ms-msword
application/vnd.ms-mswrite application/vnd.ms-powerpoint
application/vnd.ms-excel application/vnd.ms-access
application/msword-template application/excel-template
application/powerpoint-template
application/vnd.oasis.opendocument.spreadsheet
application/vnd.oasis.opendocument.formula
application/vnd.oasis.opendocument.chart
application/vnd.oasis.opendocument.image
application/vnd.oasis.opendocument.graphics
application/vnd.oasis.opendocument.presentation
application/vnd.oasis.opendocument.text-web
application/vnd.oasis.opendocument.text
application/vnd.oasis.opendocument.text-template
application/vnd.oasis.opendocument.text-master
application/vnd.oasis.opendocument.presentation-template
application/vnd.oasis.opendocument.graphics-template
application/vnd.oasis.opendocument.spreadsheet-template
application/vnd.openxmlformats-officedocument.wordprocessingml.document
)
end
def self.output_types
["application/pdf"] + input_types
end
def self.available?
command_available?(LIBREOFFICE_COMMAND)
end
def run(&block)
status = nil
# make a unique temporary directory for output, so that the filename won't collide.
Dir.mktmpdir do |work_directory|
# run command
ext = extension(output_type)
if ext
arguments = [LIBREOFFICE_COMMAND, '--headless', '-convert-to', extension(output_type), '-outdir', work_directory, input_file]
status = run_command(*arguments, &block)
# we cannot specify the name of the output file, so grab what it generated and move it to self.output_file
libreoffice_output = work_directory + '/' + replace_extension(input_file, extension(output_type))
if File.exist?(libreoffice_output)
replace_file from: libreoffice_output, to: output_file
else
msg = ["Error: Could not find libreoffice output %s \n" % output_file]
msg << arguments.join(' ')
msg += Dir[work_directory + '/*']
unless command_output.empty?
msg << 'LibreOffice said:'
msg << command_output
end
msg = msg.join("\n")
error msg
yield(msg) if block_given?
return :failure
end
else
yield('could not find extension for type %s' % output_type) if block_given?
return :failure
end
end
return status
end
end
end

View File

@ -1,3 +0,0 @@
module Media
VERSION = "0.3.0"
end

View File

@ -1,4 +0,0 @@
# desc "Explaining what the task does"
# task :media do
# # Task goes here
# end

View File

@ -1,58 +0,0 @@
Conversion Performance
======================
Convert to pdf
--------------
Some converters take ages - in particular for large documents.
Here's a few measurements for:
a) libreoffice --headless from cg
b) unoconv
c) unoconv with server running
d) unoconv with PageRange=1-1
e) unoconv with PageRange=1-1 and server running
Single page odt to pdf:
a) 0.70 sec
b) 1.46 sec
c) 0.49 sec
d) 1.47 sec
e) 0.48 sec
200 page odt to pdf:
a) 13.63 sec
b) 14.21 sec
c) 13.27 sec
d) 7.01 sec
e) 6.17 sec
Order of conversions
--------------------
The first thing we need after an upload is a small thumbnail. Unfortunately
this is also the thing that takes most steps to generate:
* doc -> pdf
* pdf -> large jpg
* large -> small jpg
We could skip the large jpg, but we need it anyway. Also the large to small jpg
step usually is the fastest.
So instead we do:
1. doc -> 1 page pdf
2. 1 page pdf -> large jpg
3. large jpg -> small jpg
4. large jpg -> medium jpg
5. doc -> full pdf
6. doc -> full odt
* doc -> x conversion cannot run in parellel. So we will want higher
priority for the first step.
* We can use a different delayed job queue for the image conversions.
* The jpg -> jpg conversion could even happen in process if needed.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

View File

@ -1,40 +0,0 @@
require 'test_helper'
require 'media/transmogrifiers/graphicsmagick'
class GraphicsMagickTransmogrifierTest < Minitest::Test
def test_scaling_png_to_jpg
input = file('lyra.png')
transmog = Media::GraphicsMagickTransmogrifier.new input_file: input,
output_type: 'image/jpg',
size: '100x100!'
assert transmog
status = transmog.run
assert_equal :success, status
assert File.exist?(transmog.output_file.to_s)
assert file_info_matches?(transmog.output_file, /JPEG/),
"output should be a jpg: #{file_info(transmog.output_file)}"
assert_equal ['100','100'], Media.dimensions(transmog.output_file),
"output should be resized: #{file_info(transmog.output_file)}"
end
# libremagic transmogrifier uses this internally.
def test_pdf_as_input
skip('ghostscript required') if ghostscript_missing?
input = file('kaos.pdf')
transmog = Media::GraphicsMagickTransmogrifier.new input_file: input,
output_type: 'image/jpg'
status = transmog.run
assert_equal :success, status
assert File.exist?(transmog.output_file.to_s)
assert file_info_matches?(transmog.output_file, /JPEG/),
"output should be a jpg: #{file_info(transmog.output_file)}"
end
def ghostscript_missing?
`which ghostscript`.empty?
end
end

View File

@ -1,25 +0,0 @@
require 'test_helper'
require 'media/transmogrifiers/libremagick'
class LibreMagickTransmogrifierTest < Minitest::Test
def test_libremagick_transmog
skip 'dependencies missing' unless klass.available?
input = file('msword.doc')
transmog =
klass.new input_file: input,
output_type: 'image/jpg'
status = transmog.run
assert_equal :success, status
assert File.exist?(transmog.output_file.to_s)
assert file_info_matches?(transmog.output_file, /JPEG/),
"output should be a jpg: #{file_info(transmog.output_file)}"
end
protected
def klass
Media::LibreMagickTransmogrifier
end
end

View File

@ -1,14 +0,0 @@
require 'test_helper'
require 'media'
class MediaTest < Minitest::Test
def test_has_dimensions
assert Media.has_dimensions? 'image/png'
end
def test_has_no_dimensions
refute Media.has_dimensions? 'text/plain'
end
end

View File

@ -1,39 +0,0 @@
require 'media/transmogrifier'
class SleepyTransmogrifier < Media::Transmogrifier
def initialize
end
def name
:sleepy
end
def self.input_types
['test']
end
def self.output_types
['test']
end
PROGRESS = [
'starting up.',
'getting ready to do some work.',
'working hard.',
'winding down.',
'that was tough.',
'all done now.'
]
def run
for str in PROGRESS
yield str
sleep 0.1
end
return :success
end
end

View File

@ -1,21 +0,0 @@
require 'pathname'
module Support
module File
def file(name)
Pathname(__FILE__) + '..' + '..' + 'files' + name
end
def file_info_matches?(file, regex)
file_info(file) =~ regex
end
def file_info(file)
`file #{file.to_s}`
end
end
end
Minitest::Test.send :include, Support::File

View File

@ -1,13 +0,0 @@
require 'minitest/autorun'
require 'media'
require 'logger'
# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
Media::TMP_PATH = '/tmp'
LOGPATH = "#{File.dirname(__FILE__)}/../log"
FileUtils.mkdir(LOGPATH) unless File.exist? LOGPATH
LOGFILE = "#{LOGPATH}/transmogrifier.log"
Media::Transmogrifier.logger = Logger.new(LOGFILE)

View File

@ -1,112 +0,0 @@
require 'test_helper'
require 'media'
require_relative 'sleepy_transmogrifier'
class TransmogrifierTest < Minitest::Test
def test_run_sleepy_transmog
progress_strings = [
'starting up.',
'getting ready to do some work.',
'working hard.',
'winding down.',
'that was tough.',
'all done now.'
]
transmog = SleepyTransmogrifier.new
i = 0
status = transmog.run do |progress|
assert_equal progress_strings[i], progress
putc ':'; STDOUT.flush
i+=1
end
assert_equal :success, status
end
def test_transmog_not_found
input = file('lyra.png')
transmog = Media.transmogrifier input_file: input,
output_type: 'image/xcf'
assert_nil transmog
end
def test_graphicsmagick_transmog
input = file('lyra.png')
transmog = Media.transmogrifier(input_file: input, output_type: 'image/jpg', size: '100x100!')
assert transmog
status = transmog.run
assert_equal :success, status
assert File.exist?(transmog.output_file.to_s)
assert file_info_matches?(transmog.output_file, /JPEG/),
"output should be a jpg: #{file_info(transmog.output_file)}"
assert_equal ['100','100'], Media.dimensions(transmog.output_file),
"output should be resized: #{file_info(transmog.output_file)}"
end
def test_with_output_file
input = file('lyra.png')
Media::TempFile.open(nil,'image/jpg') do |dest_file|
transmog = Media.transmogrifier(input_file: input, output_file: dest_file)
assert transmog, 'should find transmog'
status = transmog.run
assert_equal :success, status
assert File.exist?(dest_file.to_s)
assert file_info_matches?(dest_file, /JPEG/), "output should be a jpg: #{file_info(transmog.output_file)}"
end
end
def test_libreoffice_transmog
input = file('msword.doc')
transmog = Media.transmogrifier(input_file: input, output_type: 'application/pdf')
assert transmog
status = transmog.run
assert_equal :success, status
assert File.exist?(transmog.output_file.to_s)
assert file_info_matches?(transmog.output_file, /PDF/), "output should be a pdf: #{file_info(transmog.output_file)}"
end
def test_doc_to_jpg_twostep_transmog
skip "We're currently not exposing transformations from pdf. So this has to be done in one step"
input = file('msword.doc')
transmog = Media.transmogrifier(input_file: input, output_type: 'application/pdf')
transmog.run
transmog = Media.transmogrifier(input_file: transmog.output_file, output_type: 'image/jpg')
status = transmog.run
assert_equal :success, status
assert File.exist?(transmog.output_file.to_s)
assert file_info_matches?(transmog.output_file, /JPEG/), "output should be a jpg: #{file_info(transmog.output_file)}"
end
def test_no_pdf_transmog
input = file('kaos.pdf')
transmog = Media.transmogrifier(input_file: input, output_type: 'image/jpg')
assert_nil transmog
end
def test_libremagick_transmog
input = file('msword.doc')
transmog = Media.transmogrifier(input_file: input, output_type: 'image/jpg')
skip('libremagic is not available') unless transmog
status = transmog.run
assert_equal :success, status
assert File.exist?(transmog.output_file.to_s)
assert file_info_matches?(transmog.output_file, /JPEG/), "output should be a jpg: #{file_info(transmog.output_file)}"
end
def test_inkscape_transmog
input = file('anarchism.svg')
transmog = Media.transmogrifier(input_file: input, output_type: 'image/jpg')
assert transmog
status = transmog.run
assert_equal :success, status
assert File.exist?(transmog.output_file.to_s)
assert file_info_matches?(transmog.output_file, /JPEG/), "output should be a pdf: #{file_info(transmog.output_file)}"
end
end