class DataController < ApplicationController require 'csv' def index @page_title = t('data.export.page_title') end def import; end def csv_map if params[:file].blank? flash[:notice] = t "data.import.errors.file_blank" redirect_back fallback_location: root_path else @import_to = params[:import_to] begin # Get column headers and format as [['name', column_number]...] i = -1 @headers = import_headers(params[:file].path).collect { |v| [v, i += 1] } @headers.unshift ['', i] rescue Exception => e flash[:error] = t "data.import.errors.invalid_csv", e: e redirect_back fallback_location: root_path return end # Save file for later begin uploaded_file = params[:file] @filename = sanitize_filename(uploaded_file.original_filename) path_and_file = Rails.root.join('public', 'uploads', 'csv', @filename) File.open(path_and_file, "wb") { |f| f.write(uploaded_file.read) } rescue Exception => e flash[:error] = t "data.import.errors.save_error", path_and_file: path_and_file, e: e redirect_back fallback_location: root_path return end case @import_to when 'projects' @labels = [:name, :description] when 'todos' @labels = [:description, :context, :project, :notes, :created_at, :due, :completed_at] else flash[:error] = t "data.import.errors.invalid_destination" redirect_back fallback_location: root_path end respond_to do |format| format.html end end end def csv_import begin filename = sanitize_filename(params[:file]) path_and_file = Rails.root.join('public', 'uploads', 'csv', filename) case params[:import_to] when 'projects' count = Project.import path_and_file, params, current_user flash[:notice] = t 'data.import.projects_count', count: count when 'todos' count = Todo.import path_and_file, params, current_user if not count flash[:error] = t('data.import.errors.invalid_destination') else flash[:notice] = t 'data.import.todos_count', count: count end else flash[:error] = t('data.import.errors.invalid_destination') end rescue Exception => e flash[:error] = t 'data.import.errors.invalid_destination', e: e end File.delete(path_and_file) redirect_to import_data_path end def import_headers(file) CSV.foreach(file, headers: false) do |row| return row end end def export # Show list of formats for export end # Thanks to a tip by Gleb Arshinov # def yaml_export all_tables = {} all_tables['todos'] = current_user.todos.includes(:tags).load all_tables['contexts'] = current_user.contexts.load all_tables['projects'] = current_user.projects.load todo_tag_ids = Tag.find_by_sql([ "SELECT DISTINCT tags.id FROM tags, taggings, todos WHERE todos.user_id = ? AND tags.id = taggings.tag_id AND taggings.taggable_id = todos.id", current_user.id]) rec_todo_tag_ids = Tag.find_by_sql([ "SELECT DISTINCT tags.id FROM tags, taggings, recurring_todos WHERE recurring_todos.user_id = ? AND tags.id = taggings.tag_id AND taggings.taggable_id = recurring_todos.id", current_user.id]) tags = Tag.where("id IN (?) OR id IN (?)", todo_tag_ids, rec_todo_tag_ids) taggings = Tagging.where("tag_id IN (?) OR tag_id IN(?)", todo_tag_ids, rec_todo_tag_ids) all_tables['tags'] = tags.load all_tables['taggings'] = taggings.load all_tables['notes'] = current_user.notes.load all_tables['recurring_todos'] = current_user.recurring_todos.load result = all_tables.to_yaml # TODO: general functionality for line endings result.gsub!(/\n/, "\r\n") send_data(result, :filename => "tracks_backup.yml", :type => 'text/plain') end # export all actions as csv def csv_actions content_type = 'text/csv' CSV.generate(result = "") do |csv| csv << ["id", "Context", "Project", "Description", "Notes", "Tags", "Created at", "Due", "Completed at", "User ID", "Show from", "state"] current_user.todos.includes(:context, :project, :taggings, :tags).each do |todo| csv << [todo.id, todo.context.name, todo.project_id.nil? ? "" : todo.project.name, todo.description, todo.notes, todo.tags.collect { |t| t.name }.join(', '), todo.created_at.to_formatted_s(:db), todo.due? ? todo.due.to_formatted_s(:db) : "", todo.completed_at? ? todo.completed_at.to_formatted_s(:db) : "", todo.user_id, todo.show_from? ? todo.show_from.to_formatted_s(:db) : "", todo.state] end end send_data(result, :filename => "todos.csv", :type => content_type) end # export all notes as csv def csv_notes content_type = 'text/csv' CSV.generate(result = "") do |csv| csv << ["id", "User ID", "Project", "Note", "Created at", "Updated at"] # had to remove project include because it's association order is leaking # through and causing an ambiguous column ref even with_exclusive_scope # didn't seem to help -JamesKebinger current_user.notes.reorder("notes.created_at").each do |note| # Format dates in ISO format for easy sorting in spreadsheet Print # context and project names for easy viewing csv << [note.id, note.user_id, note.project_id = note.project_id.nil? ? "" : note.project.name, note.body, note.created_at.to_formatted_s(:db), note.updated_at.to_formatted_s(:db)] end end send_data(result, :filename => "notes.csv", :type => content_type) end def xml_export todo_tag_ids = Tag.find_by_sql([ "SELECT DISTINCT tags.id FROM tags, taggings, todos WHERE todos.user_id = ? AND tags.id = taggings.tag_id AND taggings.taggable_id = todos.id", current_user.id]) rec_todo_tag_ids = Tag.find_by_sql([ "SELECT DISTINCT tags.id FROM tags, taggings, recurring_todos WHERE recurring_todos.user_id = ? AND tags.id = taggings.tag_id AND taggings.taggable_id = recurring_todos.id", current_user.id]) tags = Tag.where("id IN (?) OR id IN (?)", todo_tag_ids, rec_todo_tag_ids) taggings = Tagging.where("tag_id IN (?) OR tag_id IN(?)", todo_tag_ids, rec_todo_tag_ids) result = "" result << current_user.todos.to_xml(:skip_instruct => true) result << current_user.contexts.to_xml(:skip_instruct => true) result << current_user.projects.to_xml(:skip_instruct => true) result << tags.to_xml(:skip_instruct => true) result << taggings.to_xml(:skip_instruct => true) result << current_user.notes.to_xml(:skip_instruct => true) result << current_user.recurring_todos.to_xml(:skip_instruct => true) result << "" send_data(result, :filename => "tracks_data.xml", :type => 'text/xml') end def yaml_form # Draw the form to input the YAML text data end # adjusts time to utc def adjust_time(timestring) if (timestring == '') || (timestring == nil) return nil else return Time.parse(timestring + 'UTC') end end def yaml_import raise t "data.import.yaml_disabled" end private def sanitize_filename(filename) filename.gsub(/[^0-9A-z.\-]/, '_') end end