CartoDB/cartodb20

View on GitHub
lib/carto/visualization_exporter.rb

Summary

Maintainability
A
35 mins
Test Coverage
require_relative 'file_system/sanitize'

module Carto
  class DataExporter
    def initialize(http_client = Carto::Http::Client.get('data_exporter', log_requests: true))
      @http_client = http_client
    end

    # Returns the file
    def export_table(user_table, folder, format)
      table_name = user_table.name

      query = %{select * from "#{table_name}"}
      url = sql_api_query_url(query, table_name, user_table.user, privacy(user_table), format)
      exported_file = "#{folder}/#{table_name}.#{format}"
      @http_client.get_file(url, exported_file, ssl_verifypeer: false, ssl_verifyhost: 0)
    end

    def export_visualization_tables(visualization, user, dir, format, user_tables_ids: nil)
      visualization.
        related_tables_readable_by(user).
        select { |ut| user_tables_ids.nil? || user_tables_ids.include?(ut.id) }.
        map { |ut| export_table(ut, dir, format) }
    end

    private

    def sql_api_query_url(query, filename, user, privacy, format)
      CartoDB::SQLApi.with_user(user, privacy).url(query, format, filename)
    end

    def privacy(user_table)
      user_table.private? ? 'private' : 'public'
    end
  end

  module ExporterConfig
    DEFAULT_EXPORTER_TMP_FOLDER = '/tmp/exporter'.freeze

    def exporter_config
      (Cartodb.config[:exporter] || {}).deep_symbolize_keys
    end

    def exporter_folder
      ensure_folder(exporter_config[:exporter_temporal_folder] || DEFAULT_EXPORTER_TMP_FOLDER)
    end

    def export_dir(visualization, base_dir: exporter_folder)
      ensure_folder("#{base_dir}/#{visualization.id}_#{String.random(10).downcase}")
    end

    # Example `parent_dir`: `export_dir(visualization, base_dir: base_dir)`
    def tmp_dir(visualization, parent_dir:)
      ensure_folder("#{parent_dir}/#{visualization.id}")
    end

    def ensure_folder(folder)
      FileUtils.mkdir_p(folder) unless Dir.exists?(folder)
      folder
    end

    def ensure_clean_folder(folder)
      FileUtils.remove_dir(folder) if Dir.exists?(folder)
      ensure_folder(folder)
    end
  end

  module VisualizationExporter
    include ExporterConfig

    DEFAULT_EXPORT_FORMAT = 'gpkg'.freeze
    EXPORT_EXTENSION = '.carto.json'.freeze
    CARTO_EXTENSION = '.carto'.freeze

    VISUALIZATION_EXTENSIONS = [Carto::VisualizationExporter::EXPORT_EXTENSION].freeze

    def self.has_visualization_extension?(filename)
      VISUALIZATION_EXTENSIONS.any? { |extension| filename =~ /#{Regexp.escape(extension)}$/ }
    end

    def export(visualization, user,
               user_tables_ids: nil,
               format: DEFAULT_EXPORT_FORMAT,
               data_exporter: DataExporter.new,
               visualization_export_service: Carto::VisualizationsExportService2.new,
               base_dir: exporter_folder)
      visualization_id = visualization.id
      export_dir = export_dir(visualization, base_dir: base_dir)
      tmp_dir = tmp_dir(visualization, parent_dir: export_dir)
      ensure_clean_folder(tmp_dir)

      data_exporter.export_visualization_tables(
        visualization,
        user,
        tmp_dir,
        format,
        user_tables_ids: user_tables_ids)

      visualization_json = visualization_export_service.export_visualization_json_string(visualization_id, user)
      visualization_json_file = "#{tmp_dir}/#{visualization_id}#{EXPORT_EXTENSION}"
      File.open(visualization_json_file, 'w') { |file| file.write(visualization_json) }

      safe_vis_name = Carto::FileSystem::Sanitize.sanitize_identifier(visualization.name)

      filename = "#{safe_vis_name} (#{Time.now.utc.strftime('on %Y-%m-%d at %H.%M.%S')})#{CARTO_EXTENSION}".freeze

      status = system('zip', '-r', filename, visualization_id, chdir: export_dir)
      raise "Error compressing export" unless status

      FileUtils.remove_dir(tmp_dir)

      "#{export_dir}/#{filename}"
    end
  end
end