crewmate/crewmate

View on GitHub
lib/google_docs.rb

Summary

Maintainability
A
35 mins
Test Coverage
# -*- encoding : utf-8 -*-
require 'oauth'

class GoogleDocs
  class ConfigurationError; end
  class RetrievalError < StandardError
    attr_accessor :response

    def initialize(response)
      @response = response
      super("Response returned: #{response.code} #{response.message}\r\n#{response.body}")
    end
  end

  RESOURCES = {
    :scope => 'https://docs.google.com/feeds/',
    :request_token_url => 'https://www.google.com/accounts/OAuthGetRequestToken',
    :access_token_url => 'https://www.google.com/accounts/OAuthGetAccessToken',
    :authorize_url => "https://www.google.com/accounts/OAuthAuthorizeToken",
    :list => 'https://docs.google.com/feeds/default/private/full',
    :create => 'https://docs.google.com/feeds/default/private/full'
  }

  TYPES = %w{document drawing file pdf presentation spreadsheet} # folder removed
  TYPES_YOU_CAN_CREATE = %w{document spreadsheet presentation}
  HEADERS = {'GData-Version' => '3.0'}
  ROLES = [:reader, :writer, :owner]
  SCOPES = [:user, :group, :domain, :default]

  def initialize(access_token, access_secret, consumer)
    @consumer = consumer
    @access_token = OAuth::AccessToken.new(@consumer, access_token, access_secret)
  end

  def list(options = {})
    url = create_url(RESOURCES[:list], options)
    res = @access_token.get(url, HEADERS)

    if res.code.to_i == 200
      return parse_list(res.body)
    else
      raise RetrievalError.new(res)
    end
  end

  def create(options)
    options.assert_valid_keys('title', 'document_type')
    raise(ArgumentError, "You must provide a title and document_type") if options[:title].nil? || options[:document_type].nil?

    body = generate_atom(options[:title], options[:document_type])
    res = @access_token.post(RESOURCES[:create], body, HEADERS.merge('Content-Type' => 'application/atom+xml'))

    if res.code.to_i == 201
      document = Nokogiri::XML(res.body)
      return parse_entry(document.xpath("//atom:entry", "atom" => "http://www.w3.org/2005/Atom"))
    else
      raise RetrievalError.new(res)
    end
  end

  def add_permission(acl_url, scope, scope_type = :user, role = :reader)
    raise ArgumentError, "Unexpected scope_type #{scope_type}" unless SCOPES.include?(scope_type)
    raise ArgumentError, "Unexpected role #{role}" unless ROLES.include?(role)

    body = generate_acl_atom(scope, scope_type, role)
    res = @access_token.post(acl_url, body, HEADERS.merge('Content-Type' => 'application/atom+xml'))

    if res.code.to_i == 201 # the acl entry was added
      return res
    elsif res.code.to_i == 409 # the acl entry already exists
      return false
    else
      raise RetrievalError.new(res)
    end
  end

  protected
    def parse_list(data)
      docs = []
      document = Nokogiri::XML(data)
      document.xpath("//atom:entry", "atom" => "http://www.w3.org/2005/Atom").each do |entry|
        doc = parse_entry(entry)
        docs << doc
      end
      docs
    end

    def create_url(url, options)
      params = options.select{|k, v| !v.nil? }.map{|k, v| "#{k}=#{CGI::escape(v)}"}.join("&")
      params.blank? ? url : "#{url}?#{params}"
    end

    def parse_entry(entry)
      doc = {}
      doc[:document_type], doc[:document_id] = entry.xpath("./gd:resourceId", "gd" => "http://schemas.google.com/g/2005").text.split(':')
      doc[:title] = entry.xpath("./atom:title/text()", "atom" => "http://www.w3.org/2005/Atom").first.text
      doc[:url] = entry.xpath("./atom:link[@rel='alternate']", "atom" => "http://www.w3.org/2005/Atom").first["href"]
      edit_entry = entry.xpath("./atom:link[@rel='edit']", "atom" => "http://www.w3.org/2005/Atom")
      doc[:edit_url] = edit_entry.first ? edit_entry.first["href"] : nil
      doc[:acl_url] = entry.xpath("./gd:feedLink[@rel='http://schemas.google.com/acl/2007#accessControlList']","gd" => "http://schemas.google.com/g/2005").first["href"]
      doc
    end

    def generate_atom(title, document_type)
      raise ArgumentError, "Invalid document type #{document_type}" unless TYPES.include?(document_type)

      atom = xml = Builder::XmlMarkup.new(:indent => 2)
      atom.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
      atom.entry('xmlns' => "http://www.w3.org/2005/Atom", 'xmlns:docs' => "http://schemas.google.com/docs/2007") do |entry|
        entry.category(
          :scheme => "http://schemas.google.com/g/2005#kind",
          :term => "http://schemas.google.com/docs/2007##{document_type}"
        )
        entry.title(title)
      end
    end

    def generate_acl_atom(scope, scope_type, role)
      atom = xml = Builder::XmlMarkup.new(:indent => 2)
      atom.entry('xmlns' => "http://www.w3.org/2005/Atom", 'xmlns:gAcl' => "http://schemas.google.com/acl/2007") do |entry|
        entry.category(
          :scheme => "http://schemas.google.com/g/2005#kind",
          :term => "http://schemas.google.com/docs/2007#accessRule"
        )
        entry.gAcl :role, :value => role.to_s
        entry.gAcl :scope, :type => scope_type.to_s, :value => scope
      end
    end
end