smalruby/smalruby-editor

View on GitHub
app/controllers/source_codes_controller.rb

Summary

Maintainability
A
25 mins
Test Coverage
# -*- coding: utf-8 -*-
require 'nkf'

class SourceCodesController < ApplicationController
  before_filter :check_whether_standalone, only: [:write, :run, :load_local]

  def index
    res = {
      localPrograms: [],
      demoPrograms: [],
    }

    select_and_get_summary = proc { |path|
      s = SourceCode.new(filename: path.basename.to_s, data: path.read)
      next if raspberrypi? && s.include_block?(/^(hardware_|pen_)/)
      s.summary
    }

    if standalone?
      res[:localPrograms] =
        local_program_paths.map(&select_and_get_summary).compact
    end

    res[:demoPrograms] =
      demo_program_paths.map(&select_and_get_summary).compact

    render json: res
  end

  def check
    render json: SourceCode.new(source_code_params).check_syntax
  end

  def create
    sc = SourceCode.create!(source_code_params)
    session[:source_code] = {
      id: sc.id,
      digest: sc.digest,
    }
    render json: { source_code: { id: sc.id } }
  end

  def download
    send_data(source_code.data,
              filename: url_encode_filename(source_code.filename),
              disposition: 'attachment',
              type: 'text/plain; charset=utf-8')

    destroy_source_code_and_delete_session(source_code)
  end

  def write
    res = { source_code: { filename: source_code.filename } }

    write_source_code(source_code)

    destroy_source_code_and_delete_session(source_code)

    render json: res
  rescue => e
    res[:source_code][:error] = e.message
    render json: res
  end

  def load
    f = params[:source_code][:file]
    info = get_file_info(f)
    if /\Atext\/plain/ =~ info[:type]
      info[:data] = NKF.nkf('-w', f.read)
    else
      info[:error] = I18n.t('.not_ruby_program',
                            scope: 'controllers.source_codes')
    end

    render json: { source_code: info }, content_type: request.format
  end

  def load_local
    filename = source_code_params[:filename]
    program_path = local_program_paths.find { |path|
      rb_basename(path) == filename
    }
    load_local_file(program_path, source_code_params[:remix])
  end

  def load_demo
    filename = source_code_params[:filename]
    program_path = demo_program_paths.find { |path|
      rb_basename(path) == filename
    }
    load_local_file(program_path, source_code_params[:remix])
  end

  def run
    source_code = SourceCode.new(source_code_params)
    if session[:username]
      s = "~/#{session[:username]}/#{source_code.filename}"
    else
      s = "~/#{source_code.filename}"
    end
    path = Pathname(s).expand_path
    env = {}
    if current_preferences["hardware_port"].present?
      env["SMALRUBOT_DEVICE"] = current_preferences["hardware_port"]
    end
    render json: source_code.run(path, env)
  end

  def to_blocks
    source_code = SourceCode.new(source_code_params)
    render text: source_code.to_blocks
  rescue
    if Rails.env == 'development'
      raise
    else
      head :bad_request
    end
  end

  private

  def source_code_params
    params.require(:source_code).permit(:data, :filename, :remix)
  end

  def get_file_info(file)
    {
      filename: file.original_filename,
      type: MIME.check(file.path).try(:content_type) || file.content_type,
      size: file.size,
    }
  end

  def url_encode_filename(filename)
    if request.env['HTTP_USER_AGENT'] =~ /MSIE|Trident/
      return ERB::Util.url_encode(filename)
    else
      filename
    end
  end

  def source_code
    return @source_code if @source_code
    sc = SourceCode.find(session[:source_code][:id])
    unless sc.digest == session[:source_code][:digest]
      fail ActiveRecord::RecordNotFound
    end
    @source_code = sc
  end

  def write_source_code(source_code)
    if session[:username]
      s = "~/#{session[:username]}/#{source_code.filename}"
    else
      s = "~/#{source_code.filename}"
    end
    path = Pathname(s).expand_path.to_s

    if File.exist?(path) && params[:force].blank?
      fail I18n.t('.exist', scope: 'controllers.source_codes')
    end

    FileUtils.mkdir_p(File.dirname(path))
    File.open(path, 'w') do |f|
      f.write(source_code.data)
    end
  end

  def destroy_source_code_and_delete_session(source_code)
    source_code.destroy

    session[:source_code] = nil
  end

  def local_program_paths
    if session[:username]
      # TODO: session[:username]の/や\をエスケープする
      s = "~/#{session[:username]}/*.rb.xml"
    else
      s = '~/*.rb.xml'
    end
    Pathname.glob(Pathname(s).expand_path)
  end

  def demo_program_paths
    Pathname.glob(Rails.root.join('demos/*.rb.xml')) +
      Pathname.glob(SmalrubyEditor.home_directory.join('demos/*.rb.xml'))
  end

  def rb_basename(path)
    path = path.basename.to_s
    path = path[0...-4] if /\.xml\z/ =~ path
    path
  end

  def load_local_file(path, remix)
    if path
      if remix == 'true'
        filename = SourceCode.make_remix_filename("~/#{session[:username]}",
                                                  path.basename.to_s)
      else
        filename = path.basename.to_s
      end
      info = {
        filename: filename,
        type: MIME.check(path.to_s).try(:content_type) || 'text/plain',
        data: NKF.nkf('-w', path.read),
        size: path.size,
      }
    else
      info = {
        filename: source_code_params[:filename],
        error: I18n.t('.not_exist', scope: 'controllers.source_codes'),
      }
    end

    render json: { source_code: info }, content_type: request.format
  end
end