amatriain/openreader

View on GitHub
FeedBunch-app/lib/opml_exporter.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

require 'nokogiri'

##
# This class has methods related to exporting a user's subscriptions in OPML format.

class OpmlExporter

  # Class constant for the directory in which OPML export files will be saved.
  FOLDER = 'opml_exports'

  # Class constant for the name with which the OPML export file will be downloaded and attached to the notification email.
  FILENAME= 'feedbunch_export.opml'

  ##
  # Enqueue a background job to export a user's subscriptions in OPML format.
  # Receives as argument the user who is doing the export.

  def self.enqueue_export_job(user)
    Rails.logger.info "Enqueuing export subscriptions job for user #{user.email} - #{user.name}"
    # Destroy the current export job state for the user. This in turn triggers a deletion of any old OPML file for the user.
    user.opml_export_job_state&.destroy
    user.create_opml_export_job_state state: OpmlExportJobState::RUNNING

    ExportSubscriptionsWorker.perform_async user.id
    return nil
  rescue => e
    Rails.logger.error "Error trying to export subscriptions in OPML format for user #{user.id} - #{user.email}"
    Rails.logger.error e.message
    Rails.logger.error e.backtrace
    user.opml_export_job_state&.destroy
    user.create_opml_export_job_state state: OpmlExportJobState::ERROR
    raise OpmlExportError.new
  end

  ##
  # Export a user's subscriptions in OPML format
  #
  # Receives as arguments:
  # - user doing the export.
  #
  # If successful, saves a file with the OPML export in the currently configured upload manager (Amazon S3 in production).
  # It also updates the state attribute of the user's opml_export_job_state to "SUCCESS".
  #
  # Returns a string with the OPML.

  def self.export(user)
    # Compose the OPML file (actually XML)
    feeds_outside_folders = user.folder_feeds Folder::NO_FOLDER, include_read: true
    # Sort feeds by title, mainly for easier testing
    feeds_outside_folders.sort_by!{|a| a.title}
    builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
      xml.opml(version: '1.0') {
        xml.head {
          xml.title 'RSS subscriptions exported by FeedBunch (feedbunch.com)'
          xml.ownerName user.name
          xml.ownerEmail user.email
          xml.dateCreated Time.zone.now.rfc2822
        }
        xml.body {
          # feeds which are not in a folder
          feeds_outside_folders.each do |feed|
            xml.outline type: 'rss', title: feed.title, text: feed.title, xmlUrl: feed.fetch_url, htmlUrl: feed.url
          end
          # folders
          user.folders.find_each do |folder|
            xml.outline(title: folder.title, text: folder.title) {
              user.folder_feeds(folder, include_read: true).find_each do |feed|
                xml.outline type: 'rss', title: feed.title, text: feed.title, xmlUrl: feed.fetch_url, htmlUrl: feed.url
              end
            }
          end
        }
      }
    end
    opml = builder.to_xml
    return opml
  end

  ##
  # Return the contents of a user's previously exported OPML file.
  # Receives as argument the user who is retrieving the export file.
  # Returns the contents of the OPML export file.
  # If an export file doesn't exist for the user, an OpmlExportDoesNotExistError will be raised.

  def self.get_export(user)
    # User should have an OPML export filename saved in the db.
    # This will only happen if the opml_export_job_state has state "SUCCESS", but the OpmlExportJobState model
    # takes care of that.
    if user.opml_export_job_state.filename.blank?
      Rails.logger.error "User #{user.id} - #{user.email} tried to download his OPML export file, but he has none"
      raise OpmlExportDoesNotExistError.new
    end

    filename = user.opml_export_job_state.filename
    # Check that the file with the saved filename actually exists.
    if !Feedbunch::Application.config.uploads_manager.exists? user.id, OpmlExporter::FOLDER, filename
      Rails.logger.error "User #{user.id} - #{user.email} tried to download his OPML export file #{filename} but it doesn't exist"
      raise OpmlExportDoesNotExistError.new
    end

    opml_data = Feedbunch::Application.config.uploads_manager.read user.id, OpmlExporter::FOLDER, filename
    return opml_data
  end

end