Commit 31a10546b031ab7472e2d86a107fa97649d88fce
1 parent
c1acd8ab
Exists in
master
and in
2 other branches
First real commit
Showing
37 changed files
with
475 additions
and
18 deletions
Show diff stats
9.77 KB
556 Bytes
31.4 KB
1.01 KB
app/assets/javascripts/application.js
... | ... | @@ -0,0 +1,26 @@ |
1 | +$(function() { | |
2 | + $("#service-video").click(function() { | |
3 | + console.log($(this).prop('defaultChecked')); | |
4 | + | |
5 | + $("#url").show('slow'); | |
6 | + $("#legend").hide('slow'); | |
7 | + }); | |
8 | + | |
9 | + $("#service-video-subtitle").click(function() { | |
10 | + console.log($(this).prop('defaultChecked')); | |
11 | + | |
12 | + $("#url").show('slow'); | |
13 | + $("#legend").show('slow'); | |
14 | + }); | |
15 | + | |
16 | + /* When user press "Back" on the browser */ | |
17 | + if ($("#service-video-subtitle")[0].checked) { | |
18 | + console.log('b'); | |
19 | + $("#service-video-subtitle").click(); | |
20 | + } | |
21 | + | |
22 | + if ($("#service-video")[0].checked) { | |
23 | + console.log('a'); | |
24 | + $("#service-video").click(); | |
25 | + } | |
26 | +}); | |
0 | 27 | \ No newline at end of file | ... | ... |
app/assets/stylesheets/application.css.scss
app/assets/stylesheets/static.css.scss
app/controllers/application_controller.rb
... | ... | @@ -2,4 +2,14 @@ class ApplicationController < ActionController::Base |
2 | 2 | # Prevent CSRF attacks by raising an exception. |
3 | 3 | # For APIs, you may want to use :null_session instead. |
4 | 4 | protect_from_forgery with: :exception |
5 | + | |
6 | + | |
7 | +protected | |
8 | + def check_vlibras_api_status | |
9 | + unless ApiClient.check_status | |
10 | + flash[:error] = 'SEAaaS API is down :(' | |
11 | + redirect_to home_path | |
12 | + end | |
13 | + end | |
14 | + | |
5 | 15 | end | ... | ... |
app/controllers/static/v_libras_controller.rb
... | ... | @@ -0,0 +1,23 @@ |
1 | +class VLibras::RequestsController < ApplicationController | |
2 | + before_filter :check_vlibras_api_status | |
3 | + | |
4 | + def rapid | |
5 | + @request = VLibras::Request.new | |
6 | + end | |
7 | + | |
8 | + def create | |
9 | + @request = VLibras::Request.build_from_params(params, current_user) | |
10 | + | |
11 | + if @request.save | |
12 | + @request.perform_request | |
13 | + | |
14 | + flash[:success] = 'Sua requisição foi submetida com sucesso!' | |
15 | + redirect_to :action => :rapid | |
16 | + else | |
17 | + flash[:error] = 'Algo deu errado com a sua requisição.' | |
18 | + render :action => :rapid | |
19 | + end | |
20 | + | |
21 | + | |
22 | + end | |
23 | +end | ... | ... |
app/helpers/application_helper.rb
... | ... | @@ -12,4 +12,35 @@ module ApplicationHelper |
12 | 12 | html.html_safe |
13 | 13 | end |
14 | 14 | |
15 | + def bootstrap_class_for(flash_type) | |
16 | + case flash_type.to_s | |
17 | + when "success" | |
18 | + "alert-success" # Green | |
19 | + when "error" | |
20 | + "alert-danger" # Red | |
21 | + when "alert" | |
22 | + "alert-warning" # Yellow | |
23 | + when "notice" | |
24 | + "alert-info" # Blue | |
25 | + else | |
26 | + flash_type | |
27 | + end | |
28 | + end | |
29 | + | |
30 | + def request_status_label(status) | |
31 | + classes = [ 'label' ] | |
32 | + | |
33 | + case status | |
34 | + when 'created' | |
35 | + when 'processing' | |
36 | + classes << 'label-warning' | |
37 | + when 'error' | |
38 | + classes << 'label-important' | |
39 | + when 'success' | |
40 | + classes << 'label-success' | |
41 | + end | |
42 | + | |
43 | + content_tag(:div, t(status, scope: 'status'), :class => classes) | |
44 | + end | |
45 | + | |
15 | 46 | end | ... | ... |
app/models/.keep
app/models/role.rb
1 | +# == Schema Information | |
2 | +# | |
3 | +# Table name: roles | |
4 | +# | |
5 | +# id :integer not null, primary key | |
6 | +# name :string(255) | |
7 | +# resource_id :integer | |
8 | +# resource_type :string(255) | |
9 | +# created_at :datetime | |
10 | +# updated_at :datetime | |
11 | +# | |
12 | +# Indexes | |
13 | +# | |
14 | +# index_roles_on_name (name) | |
15 | +# index_roles_on_name_and_resource_type_and_resource_id (name,resource_type,resource_id) | |
16 | +# | |
17 | + | |
1 | 18 | class Role < ActiveRecord::Base |
2 | 19 | has_and_belongs_to_many :users, :join_table => :users_roles |
3 | 20 | belongs_to :resource, :polymorphic => true | ... | ... |
app/models/user.rb
1 | +# == Schema Information | |
2 | +# | |
3 | +# Table name: users | |
4 | +# | |
5 | +# id :integer not null, primary key | |
6 | +# name :string(255) | |
7 | +# email :string(255) default(""), not null | |
8 | +# encrypted_password :string(255) default(""), not null | |
9 | +# reset_password_token :string(255) | |
10 | +# reset_password_sent_at :datetime | |
11 | +# remember_created_at :datetime | |
12 | +# sign_in_count :integer default(0), not null | |
13 | +# current_sign_in_at :datetime | |
14 | +# last_sign_in_at :datetime | |
15 | +# current_sign_in_ip :string(255) | |
16 | +# last_sign_in_ip :string(255) | |
17 | +# created_at :datetime | |
18 | +# updated_at :datetime | |
19 | +# | |
20 | +# Indexes | |
21 | +# | |
22 | +# index_users_on_email (email) UNIQUE | |
23 | +# index_users_on_reset_password_token (reset_password_token) UNIQUE | |
24 | +# | |
25 | + | |
1 | 26 | class User < ActiveRecord::Base |
2 | 27 | rolify |
28 | + | |
29 | + has_many :requests, :class => VLibras::Request, :foreign_key => :owner_id | |
30 | + has_many :videos, :through => :requests, :class => VLibras::Video | |
31 | + | |
3 | 32 | # Include default devise modules. Others available are: |
4 | 33 | # :confirmable, :lockable, :timeoutable and :omniauthable |
5 | 34 | devise :database_authenticatable, | ... | ... |
... | ... | @@ -0,0 +1,58 @@ |
1 | +# == Schema Information | |
2 | +# | |
3 | +# Table name: v_libras_requests | |
4 | +# | |
5 | +# id :integer not null, primary key | |
6 | +# status :string(255) | |
7 | +# service_type :string(255) | |
8 | +# owner_id :integer | |
9 | +# params :text | |
10 | +# response :text | |
11 | +# created_at :datetime | |
12 | +# updated_at :datetime | |
13 | +# | |
14 | + | |
15 | +class VLibras::Request < ActiveRecord::Base | |
16 | + serialize :params | |
17 | + | |
18 | + belongs_to :owner, :class => User | |
19 | + | |
20 | + has_one :video, :class => VLibras::Video | |
21 | + | |
22 | + validates :service_type, | |
23 | + presence: true, | |
24 | + inclusion: { in: %w(video-subtitle video), message: "%{value} is not a valid service type" } | |
25 | + | |
26 | + validates :status, | |
27 | + presence: true, | |
28 | + inclusion: { in: %w(created processing error success), message: "%{value} is not a valid service type" } | |
29 | + | |
30 | + before_save :default_values | |
31 | + | |
32 | + default_scope { order('created_at DESC') } | |
33 | + | |
34 | + def self.build_from_params(params, user) | |
35 | + request = self.new | |
36 | + | |
37 | + request.service_type = params[:service] | |
38 | + request.owner = user | |
39 | + | |
40 | + request.params = params[:params] | |
41 | + | |
42 | + request | |
43 | + end | |
44 | + | |
45 | + def perform_request | |
46 | + logger.debug '[VLibras::Request] Starting request' | |
47 | + | |
48 | + ApiClient::Client.submit(self) | |
49 | + | |
50 | + logger.debug '[VLibras::Request] Request done' | |
51 | + end | |
52 | + handle_asynchronously :perform_request | |
53 | + | |
54 | +private | |
55 | + def default_values | |
56 | + self.status ||= 'created' | |
57 | + end | |
58 | +end | ... | ... |
... | ... | @@ -0,0 +1,14 @@ |
1 | +# == Schema Information | |
2 | +# | |
3 | +# Table name: v_libras_videos | |
4 | +# | |
5 | +# id :integer not null, primary key | |
6 | +# request_id :integer | |
7 | +# url :string(255) | |
8 | +# created_at :datetime | |
9 | +# updated_at :datetime | |
10 | +# | |
11 | + | |
12 | +class VLibras::Video < ActiveRecord::Base | |
13 | + belongs_to :request, :class => VLibras::Request | |
14 | +end | ... | ... |
app/views/layouts/application.html.erb
... | ... | @@ -11,12 +11,11 @@ |
11 | 11 | <div class="navbar-inner"> |
12 | 12 | <div class="container"> |
13 | 13 | <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> |
14 | - | |
15 | 14 | <span class="icon-bar"></span> |
16 | 15 | <span class="icon-bar"></span> |
17 | 16 | <span class="icon-bar"></span> |
18 | 17 | </a> |
19 | - <%= link_to "GTAaaS", home_path, :target => "blank", :class => "brand" %> | |
18 | + <%= link_to "GTAaaS", home_path, :class => "brand" %> | |
20 | 19 | |
21 | 20 | <%- if current_user.present? %> |
22 | 21 | <div class="nav-collapse"> |
... | ... | @@ -27,14 +26,14 @@ |
27 | 26 | <a class="dropdown-toggle" href="#" data-toggle="dropdown"><%= t('shared.vlibras') %><strong class="caret"></strong></a> |
28 | 27 | |
29 | 28 | <ul class="dropdown-menu"> |
30 | - <li><%= link_to t('wikivideos.my_videos'), '#' %></li> | |
29 | + <li><%= link_to t('wikivideos.my_videos'), v_libras_videos_path %></li> | |
31 | 30 | <li><%= link_to t('videos.new'), '#' %></li> |
32 | 31 | <li class="divider"></li> |
33 | - <li><%= link_to t('shared.form_alternative'), rapid_vlibras_path %></li> | |
32 | + <li><%= link_to t('shared.form_alternative'), rapid_v_libras_requests_path %></li> | |
34 | 33 | </ul> |
35 | 34 | </li> |
36 | 35 | |
37 | - <li class="dropdown"> | |
36 | + <li class="dropdown hidden"> | |
38 | 37 | <a class="dropdown-toggle" href="#" data-toggle="dropdown"><%= t('shared.wikilibras') %><strong class="caret"></strong></a> |
39 | 38 | |
40 | 39 | <ul class="dropdown-menu"> |
... | ... | @@ -44,7 +43,7 @@ |
44 | 43 | <li><%= link_to t('wikivideos.dicionario_de_dados'), '#' %></li> |
45 | 44 | </ul> |
46 | 45 | </li> |
47 | - <li><%= link_to t('shared.slibras') %></li> | |
46 | + <li class="hidden"><%= link_to t('shared.slibras') %></li> | |
48 | 47 | <li><%= link_to t('shared.about'), "http://gtaaas.lavid.ufpb.br/projeto", :target => "blank" %></li> |
49 | 48 | </ul> |
50 | 49 | |
... | ... | @@ -74,12 +73,21 @@ |
74 | 73 | |
75 | 74 | <div class="container"> |
76 | 75 | <div class="body"></div> |
76 | + | |
77 | + <% if content_for?(:menu) %> | |
77 | 78 | <div class="breadcrumb"> |
78 | 79 | <%= yield :menu %> |
79 | 80 | </div> |
81 | + <% end %> | |
80 | 82 | |
81 | 83 | <div class="container-fluid"> |
82 | - <%#= render_breadcrumbs %> | |
84 | + <% flash.each do |type, message| %> | |
85 | + <div class="alert <%= bootstrap_class_for(type) %> fade in"> | |
86 | + <button class="close" data-dismiss="alert">×</button> | |
87 | + <%= message %> | |
88 | + </div> | |
89 | + <% end %> | |
90 | + | |
83 | 91 | <%= yield %> |
84 | 92 | </div> |
85 | 93 | <footer> | ... | ... |
... | ... | @@ -0,0 +1,49 @@ |
1 | += javascript_include_tag "static/v_libras/rapid" | |
2 | + | |
3 | +.login | |
4 | + %h2 | |
5 | + = t('videos.new') | |
6 | + | |
7 | + = form_tag v_libras_requests_path, method: :post do |f| | |
8 | + - if @request.errors.any? | |
9 | + #error_explanation | |
10 | + %p/ | |
11 | + %h4 | |
12 | + = pluralize(@request.errors.count, "erro") | |
13 | + impede o vídeo de ser salvo: | |
14 | + %ul | |
15 | + - @request.errors.full_messages.each do |msg| | |
16 | + %li.error= msg | |
17 | + | |
18 | + .field | |
19 | + = label_tag :service, t('videos.video_type'), :class => 'bold' | |
20 | + | |
21 | + %label.radio | |
22 | + = radio_button_tag :service, 'video', false, :id => 'service-video' | |
23 | + Voz | |
24 | + %label.radio | |
25 | + = radio_button_tag :service, 'video-subtitle', false, :id => 'service-video-subtitle' | |
26 | + Legenda (.SRT) | |
27 | + | |
28 | + #url.hide | |
29 | + = label_tag :url, t('videos.url'), :class => "bold" | |
30 | + = file_field_tag :url, :onchange => "return check_video(this)" | |
31 | + #legend.hide | |
32 | + = label_tag :legend, t('videos.subtitle'), :class => "bold" | |
33 | + = file_field_tag :legend, :prompt => "LEGENDA", :onchange => "return check_subtitle(this)" | |
34 | + .field | |
35 | + = label_tag 'params[:window_size]', t('videos.window_size'), :class => "bold" | |
36 | + = select_tag 'params[:window_size]', options_for_select([['Pequena', 'pequeno'], ['Média', 'medio'], ['Grande', 'grande']]) | |
37 | + .field | |
38 | + %p | |
39 | + %b | |
40 | + = label_tag 'params[:window_position]', t('videos.window_position'), :class => "bold" | |
41 | + = select_tag 'params[:window_position]', options_for_select([[t('videos.top_left'), | |
42 | + 'superior-esquerdo'], [t('videos.top_right'), 'superior-direito'], | |
43 | + [t('videos.bottom_right'),'inferior-direito'], [t('videos.bottom_left'), 'inferior-esquerdo']]) | |
44 | + .field | |
45 | + = label_tag 'params[:transparency]', t('videos.transparency'), :class => "bold" | |
46 | + = select_tag 'params[:transparency]', options_for_select([['Opaco', 'opaco'], ['Transparente', 'transparente']]) | |
47 | + = hidden_field_tag :user_id, :value => current_user.id | |
48 | + = submit_tag "Confirmar", :class => "btn btn-primary actions" | |
49 | + | ... | ... |
... | ... | @@ -0,0 +1,31 @@ |
1 | +.breadcrumb | |
2 | + %h3= t('videos.list') | |
3 | + | |
4 | +.row | |
5 | + - @videos.each do |video| | |
6 | + .span3 | |
7 | + = link_to image_tag("avatar.png"), video_path(video) | |
8 | + %p | |
9 | + = link_to t('videos.show'), video_path(video), class: "btn btn-success" | |
10 | + = link_to t('videos.delete'), video_path(video), confirm: t('shared.confirm_delete'), method: :delete, class: "btn btn-danger" | |
11 | + | |
12 | + | |
13 | +.breadcrumb.requests | |
14 | + %h4= t('requests.list') | |
15 | + | |
16 | +.row | |
17 | + %table.table.table-hover.table-striped.table-requests.span6.offset3 | |
18 | + %thead | |
19 | + %tr | |
20 | + %th Situação | |
21 | + %th Vídeo | |
22 | + %th Criado há | |
23 | + | |
24 | + - @requests.each do |request| | |
25 | + %tr | |
26 | + %td.span1= request_status_label(request.status) | |
27 | + %td.span2 Vídeo | |
28 | + %td.span3= time_ago_in_words(request.created_at) | |
29 | + | |
30 | + | |
31 | + | ... | ... |
config/application.rb
... | ... | @@ -13,6 +13,8 @@ module Vlibras |
13 | 13 | # Application configuration should go into files in config/initializers |
14 | 14 | # -- all .rb files in that directory are automatically loaded. |
15 | 15 | |
16 | + config.autoload_paths += %W(#{config.root}/lib) | |
17 | + | |
16 | 18 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. |
17 | 19 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. |
18 | 20 | # config.time_zone = 'Central Time (US & Canada)' | ... | ... |
config/environments/development.rb
... | ... | @@ -42,6 +42,7 @@ Rails.application.configure do |
42 | 42 | config.action_mailer.default_url_options = { :host => 'localhost:3000' } |
43 | 43 | config.action_mailer.delivery_method = :smtp |
44 | 44 | config.action_mailer.raise_delivery_errors = true |
45 | + | |
45 | 46 | # Send email in development mode? |
46 | 47 | config.action_mailer.perform_deliveries = true |
47 | 48 | ... | ... |
config/locales/pt-BR.yml
... | ... | @@ -3,6 +3,12 @@ |
3 | 3 | # Author Igor Amorim - www.igoramorim.com |
4 | 4 | # |
5 | 5 | pt-BR: |
6 | + status: | |
7 | + created: 'Criado' | |
8 | + processing: 'Processando' | |
9 | + error: 'Erro' | |
10 | + success: 'Sucesso' | |
11 | + | |
6 | 12 | shared: |
7 | 13 | main: "MENU" |
8 | 14 | about: "SOBRE O PROJETO" |
... | ... | @@ -30,6 +36,9 @@ pt-BR: |
30 | 36 | created_at: "Criado em" |
31 | 37 | updated_at: "Atualizado em" |
32 | 38 | |
39 | + requests: | |
40 | + list: "Lista de Requisições" | |
41 | + | |
33 | 42 | videos: |
34 | 43 | new: "Novo Vídeo" |
35 | 44 | edit: "Editando Vídeo" | ... | ... |
config/routes.rb
... | ... | @@ -5,5 +5,12 @@ Rails.application.routes.draw do |
5 | 5 | |
6 | 6 | root 'static#home', as: :home |
7 | 7 | |
8 | - get '/vlibras/rapid', as: :rapid_vlibras, to: 'static/v_libras#rapid' | |
8 | + namespace :v_libras do | |
9 | + resources :requests, :only => [ :new, :create ] do | |
10 | + get 'rapid', on: :collection | |
11 | + end | |
12 | + | |
13 | + resources :videos, :only => [ :index ] | |
14 | + end | |
15 | + | |
9 | 16 | end | ... | ... |
... | ... | @@ -0,0 +1,15 @@ |
1 | +class CreateVLibrasRequests < ActiveRecord::Migration | |
2 | + def change | |
3 | + create_table :v_libras_requests do |t| | |
4 | + t.string :status | |
5 | + t.string :service_type | |
6 | + | |
7 | + t.references :owner | |
8 | + t.text :params | |
9 | + | |
10 | + t.text :response | |
11 | + | |
12 | + t.timestamps | |
13 | + end | |
14 | + end | |
15 | +end | ... | ... |
... | ... | @@ -0,0 +1,22 @@ |
1 | +class CreateDelayedJobs < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + create_table :delayed_jobs, :force => true do |table| | |
4 | + table.integer :priority, :default => 0, :null => false # Allows some jobs to jump to the front of the queue | |
5 | + table.integer :attempts, :default => 0, :null => false # Provides for retries, but still fail eventually. | |
6 | + table.text :handler, :null => false # YAML-encoded string of the object that will do work | |
7 | + table.text :last_error # reason for last failure (See Note below) | |
8 | + table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. | |
9 | + table.datetime :locked_at # Set when a client is working on this object | |
10 | + table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) | |
11 | + table.string :locked_by # Who is working on this object (if locked) | |
12 | + table.string :queue # The name of the queue this job is in | |
13 | + table.timestamps | |
14 | + end | |
15 | + | |
16 | + add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority' | |
17 | + end | |
18 | + | |
19 | + def self.down | |
20 | + drop_table :delayed_jobs | |
21 | + end | |
22 | +end | ... | ... |
db/schema.rb
... | ... | @@ -11,7 +11,23 @@ |
11 | 11 | # |
12 | 12 | # It's strongly recommended that you check this file into your version control system. |
13 | 13 | |
14 | -ActiveRecord::Schema.define(version: 20140513072121) do | |
14 | +ActiveRecord::Schema.define(version: 20140522160613) do | |
15 | + | |
16 | + create_table "delayed_jobs", force: true do |t| | |
17 | + t.integer "priority", default: 0, null: false | |
18 | + t.integer "attempts", default: 0, null: false | |
19 | + t.text "handler", null: false | |
20 | + t.text "last_error" | |
21 | + t.datetime "run_at" | |
22 | + t.datetime "locked_at" | |
23 | + t.datetime "failed_at" | |
24 | + t.string "locked_by" | |
25 | + t.string "queue" | |
26 | + t.datetime "created_at" | |
27 | + t.datetime "updated_at" | |
28 | + end | |
29 | + | |
30 | + add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority" | |
15 | 31 | |
16 | 32 | create_table "roles", force: true do |t| |
17 | 33 | t.string "name" |
... | ... | @@ -50,4 +66,21 @@ ActiveRecord::Schema.define(version: 20140513072121) do |
50 | 66 | |
51 | 67 | add_index "users_roles", ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id" |
52 | 68 | |
69 | + create_table "v_libras_requests", force: true do |t| | |
70 | + t.string "status" | |
71 | + t.string "service_type" | |
72 | + t.integer "owner_id" | |
73 | + t.text "params" | |
74 | + t.text "response" | |
75 | + t.datetime "created_at" | |
76 | + t.datetime "updated_at" | |
77 | + end | |
78 | + | |
79 | + create_table "v_libras_videos", force: true do |t| | |
80 | + t.integer "request_id" | |
81 | + t.string "url" | |
82 | + t.datetime "created_at" | |
83 | + t.datetime "updated_at" | |
84 | + end | |
85 | + | |
53 | 86 | end | ... | ... |
... | ... | @@ -0,0 +1,34 @@ |
1 | +# NOTE: only doing this in development as some production environments (Heroku) | |
2 | +# NOTE: are sensitive to local FS writes, and besides -- it's just not proper | |
3 | +# NOTE: to have a dev-mode tool do its thing in production. | |
4 | +if(Rails.env.development?) | |
5 | + task :set_annotation_options do | |
6 | + # You can override any of these by setting an environment variable of the | |
7 | + # same name. | |
8 | + Annotate.set_defaults({ | |
9 | + 'position_in_routes' => "before", | |
10 | + 'position_in_class' => "before", | |
11 | + 'position_in_test' => "before", | |
12 | + 'position_in_fixture' => "before", | |
13 | + 'position_in_factory' => "before", | |
14 | + 'show_indexes' => "true", | |
15 | + 'simple_indexes' => "false", | |
16 | + 'model_dir' => "app/models", | |
17 | + 'include_version' => "false", | |
18 | + 'require' => "", | |
19 | + 'exclude_tests' => "true", | |
20 | + 'exclude_fixtures' => "true", | |
21 | + 'exclude_factories' => "true", | |
22 | + 'ignore_model_sub_dir' => "false", | |
23 | + 'skip_on_db_migrate' => "false", | |
24 | + 'format_bare' => "true", | |
25 | + 'format_rdoc' => "false", | |
26 | + 'format_markdown' => "false", | |
27 | + 'sort' => "false", | |
28 | + 'force' => "false", | |
29 | + 'trace' => "false", | |
30 | + }) | |
31 | + end | |
32 | + | |
33 | + Annotate.load_tasks | |
34 | +end | ... | ... |