require 'oauth' require 'json' module Logworm class ForbiddenAccessException < Exception ; end class DatabaseException < Exception ; end class InvalidQueryException < Exception ; end class DB DEFAULT_HOST = "db.logworm.com" URL_FORMAT = /lw:\/\/([^:]+):([^!]+)!([^:]+):([^@]+)@([^\/]+)\// # URI: lw://:!:@db.logworm.com/ def initialize(url) match = DB.parse_url(url) raise ForbiddenAccessException.new("Incorrect URL Format #{url}") unless match and match.size == 6 keys = match[1..4] raise ForbiddenAccessException.new("Incorrect Keys") unless keys.is_a? Array and keys.size == 4 @consumer_key, @consumer_secret, @token, @token_secret = keys raise ForbiddenAccessException.new("Missing consumer keys") if @consumer_key.nil? or @consumer_secret.nil? raise ForbiddenAccessException.new("Missing tokens") if @token.nil? or @token_secret.nil? @connection = OAuth::AccessToken.new(OAuth::Consumer.new(@consumer_key, @consumer_secret), @token, @token_secret) @host = "http://" + match[5] end def self.with_tokens(token, token_secret) consumer_key = ENV["#{ENV['APP_ID']}_APPS_KEY"] consumer_secret = ENV["#{ENV['APP_ID']}_APPS_SECRET"] DB.new("lw://#{consumer_key}:#{consumer_secret}!#{token}:#{token_secret}@#{DEFAULT_HOST}/") end def self.from_config # Try with URL from the environment return DB.new(ENV['LOGWORM_URL']) if ENV['LOGWORM_URL'] and DB.parse_url(ENV['LOGWORM_URL']) # Try with configuration file config = Logworm::Config.instance return DB.new(config.url) if config.file_found? and DB.parse_url(config.url) # Try with Heroku configuration otherwise config_vars = %x[heroku config --long] || "" m = config_vars.match(Regexp.new("LOGWORM_URL\\s+=>\\s+([^\\n]+)")) config.save(m[1]) and return DB.new(m[1]) if m and DB.parse_url(m[1]) nil end def self.from_config_or_die db = self.from_config raise "The application is not properly configured. Either use 'heroku addon:add' to add logworm to your app, or save your project's credentials into the .logworm file" unless db db end def tables() res = db_call(:get, "#{@host}/") end def query(table, cond) res = db_call(:post, "#{@host}/queries", {:table => table, :query => cond}) end def results(uri) res = db_call(:get, uri) raise InvalidQueryException.new("#{res['error']}") if res['error'] res["results"] = JSON.parse(res["results"]) res end def batch_log(entries) db_call(:post, "#{@host}/log", {:entries => $lr_queue.to_json}) end private def db_call(method, uri, params = {}) begin res = @connection.send(method, uri, params) rescue SocketError raise DatabaseException end raise DatabaseException if res.code.to_i == 404 raise DatabaseException.new("Server returned: #{res.body}") if res.code.to_i == 500 raise ForbiddenAccessException if res.code.to_i == 403 raise InvalidQueryException.new("#{res.body}") if res.code.to_i == 400 begin JSON.parse(res.body) rescue Exception => e raise DatabaseException.new("Database reponse cannot be parsed: #{e}") end end def self.parse_url(url) url.match(URL_FORMAT) end end end