flying-sphinx 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .bundle
2
+ Gemfile.lock
3
+ *.tmproj
4
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENCE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Pat Allan
1
+ Copyright (c) 2010-2011 Pat Allan
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ begin
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+ rescue LoadError
6
+ puts "Although not required, it's recommended you use bundler during development"
7
+ end
8
+
9
+ require 'rake'
10
+
11
+ task :default => :test
12
+ task :test => :spec
13
+
14
+ require 'rspec/core/rake_task'
15
+
16
+ RSpec::Core::RakeTask.new do |t|
17
+ t.pattern = 'spec/**/*_spec.rb'
18
+ end
19
+
20
+ RSpec::Core::RakeTask.new(:rspec) do |t|
21
+ t.pattern = 'spec/**/*_spec.rb'
22
+ t.rcov = true
23
+ t.rcov_opts = [
24
+ '--exclude', 'spec', '--exclude', 'gems',
25
+ '--exclude', 'riddle', '--exclude', 'ruby'
26
+ ]
27
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.4
1
+ 0.5.0
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = 'flying-sphinx'
4
+ s.version = '0.5.0'
5
+ s.authors = ['Pat Allan']
6
+ s.email = 'pat@freelancing-gods.com'
7
+ s.date = '2011-05-12'
8
+ s.summary = 'Sphinx in the Cloud'
9
+ s.description = 'Hooks Thinking Sphinx into the Flying Sphinx service'
10
+ s.homepage = 'https://flying-sphinx.com'
11
+
12
+ s.extra_rdoc_files = ['README.textile']
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.require_paths = ['lib']
16
+
17
+ s.rubygems_version = %q{1.3.7}
18
+
19
+ s.add_runtime_dependency 'thinking-sphinx', ['>= 0']
20
+ s.add_runtime_dependency 'net-ssh', ['~> 2.0.23']
21
+ s.add_runtime_dependency 'multi_json', ['~> 1.0.1']
22
+ s.add_runtime_dependency 'faraday', ['~> 0.6.1']
23
+ s.add_runtime_dependency 'faraday_middleware', ['~> 0.6.3']
24
+ s.add_runtime_dependency 'rash', ['~> 0.3.0']
25
+
26
+ s.add_development_dependency 'yajl-ruby', ['~> 0.8.2']
27
+ s.add_development_dependency 'rspec', ['~> 2.5.0']
28
+ s.add_development_dependency 'rcov', ['~> 0.9.9']
29
+ s.add_development_dependency 'fakeweb', ['~> 1.3.0']
30
+ s.add_development_dependency 'fakeweb-matcher', ['~> 1.2.2']
31
+ s.add_development_dependency 'delayed_job', ['~> 2.1.4']
32
+
33
+ s.post_install_message = <<-MESSAGE
34
+ If you're upgrading, you should rebuild your Sphinx setup when deploying:
35
+
36
+ $ heroku rake fs:rebuild
37
+ MESSAGE
38
+ end
data/lib/flying_sphinx.rb CHANGED
@@ -2,7 +2,6 @@ module FlyingSphinx
2
2
  #
3
3
  end
4
4
 
5
- require 'httparty'
6
5
  require 'net/ssh'
7
6
  require 'riddle/0.9.9'
8
7
 
@@ -1,33 +1,79 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+
1
4
  class FlyingSphinx::API
2
- include HTTParty
3
-
4
- APIServer = 'https://flying-sphinx.com/heroku'
5
-
6
- attr_reader :api_key, :identifier
7
-
8
- def initialize(identifier, api_key)
9
- @api_key = api_key
5
+
6
+ APIServer = 'https://flying-sphinx.com'
7
+ APIPath = "/api/my/app"
8
+ APIVersion = 2
9
+
10
+ attr_reader :api_key, :identifier, :adapter
11
+
12
+ def initialize(identifier, api_key, adapter = Faraday.default_adapter)
13
+ @api_key = api_key
10
14
  @identifier = identifier
15
+ @adapter = adapter
11
16
  end
12
-
17
+
13
18
  def get(path, data = {})
14
- self.class.get "#{APIServer}#{path}", :query => data.merge(api_options)
19
+ log('GET', path, data) do
20
+ connection.get do |request|
21
+ request.url normalize_path(path), data
22
+ end
23
+ end
15
24
  end
16
-
25
+
17
26
  def post(path, data = {})
18
- self.class.post "#{APIServer}#{path}", :body => data.merge(api_options)
27
+ log('POST', path, data) do
28
+ connection.post normalize_path(path), data
29
+ end
19
30
  end
20
-
31
+
21
32
  def put(path, data = {})
22
- self.class.put "#{APIServer}#{path}", :body => data.merge(api_options)
33
+ log('PUT', path, data) do
34
+ connection.put normalize_path(path), data
35
+ end
23
36
  end
24
-
37
+
25
38
  private
26
-
27
- def api_options
39
+
40
+ def normalize_path(path)
41
+ path = (path == '/' ? nil : "/#{path}")
42
+ "#{APIPath}#{path}"
43
+ end
44
+
45
+ def api_headers
28
46
  {
29
- :api_key => api_key,
30
- :identifier => identifier
47
+ 'Accept' => "application/vnd.flying-sphinx-v#{APIVersion}+json",
48
+ 'X-Flying-Sphinx-Token' => "#{identifier}:#{api_key}"
49
+ }
50
+ end
51
+
52
+ def connection(connection_options = {})
53
+ options = {
54
+ :ssl => {:verify => false},
55
+ :url => APIServer,
56
+ :headers => api_headers
31
57
  }
58
+
59
+ Faraday.new(options) do |builder|
60
+ builder.use Faraday::Request::UrlEncoded
61
+ builder.use Faraday::Response::Rashify
62
+ builder.use Faraday::Response::ParseJson
63
+ builder.adapter(adapter)
64
+ end
65
+ end
66
+
67
+ def log(method, path, data = {}, option = {}, &block)
68
+ return block.call unless log?
69
+
70
+ puts "API Request: #{method} '#{path}'; params: #{data.inspect}"
71
+ response = block.call
72
+ puts "API Response: #{response.body.to_hash.inspect}"
73
+ return response
74
+ end
75
+
76
+ def log?
77
+ ENV['VERBOSE_LOGGING'].present?
32
78
  end
33
79
  end
@@ -1,104 +1,131 @@
1
1
  class FlyingSphinx::Configuration
2
2
  attr_reader :identifier, :api_key, :host, :port, :database_port, :mem_limit
3
-
3
+
4
4
  def initialize(identifier = nil, api_key = nil)
5
5
  @identifier = identifier || identifier_from_env
6
6
  @api_key = api_key || api_key_from_env
7
-
7
+
8
8
  set_from_server
9
9
  setup_environment_settings
10
10
  end
11
-
11
+
12
12
  def api
13
13
  @api ||= FlyingSphinx::API.new(identifier, api_key)
14
14
  end
15
-
15
+
16
16
  def sphinx_configuration
17
17
  thinking_sphinx.generate
18
18
  set_database_settings
19
-
19
+
20
20
  riddle.render
21
21
  end
22
-
22
+
23
23
  def start_sphinx
24
- api.post('/app/start')
24
+ api.post('start')
25
25
  end
26
-
26
+
27
27
  def stop_sphinx
28
- api.post('/app/stop')
28
+ api.post('stop')
29
29
  end
30
30
 
31
- private
32
-
33
- def set_from_server
34
- response = api.get('/app')
35
- raise 'Invalid Flying Sphinx credentials' if response.code == 403
36
-
37
- json = JSON.parse response.body
38
-
39
- @host = json['server']
40
- @port = json['port']
41
- @database_port = json['database_port']
42
- @mem_limit = json['mem_limit']
31
+ def client_key
32
+ "#{identifier}:#{api_key}"
33
+ end
34
+
35
+ def output_recent_actions
36
+ api.get('actions').body.each do |action|
37
+ puts "#{action.created_at} #{action.name}"
38
+ end
43
39
  end
44
40
 
41
+ private
42
+
43
+ def set_from_server
44
+ response = api.get '/'
45
+ raise 'Invalid Flying Sphinx credentials' if response.status == 403
46
+
47
+ @host = response.body.server
48
+ @port = response.body.port
49
+ @database_port = response.body.database_port
50
+ @mem_limit = response.body.mem_limit
51
+ rescue
52
+ # If the central Flying Sphinx server is down, let's use the environment
53
+ # variables so searching is still going to work.
54
+ @host = host_from_env
55
+ @port = port_from_env
56
+ end
57
+
45
58
  def base_path
46
59
  "/mnt/sphinx/flying-sphinx/#{identifier}"
47
60
  end
48
-
61
+
49
62
  def log_path
50
63
  "#{base_path}/log"
51
64
  end
52
-
65
+
53
66
  def thinking_sphinx
54
67
  ThinkingSphinx::Configuration.instance
55
68
  end
56
-
69
+
57
70
  def riddle
58
71
  thinking_sphinx.configuration
59
72
  end
60
-
73
+
61
74
  def setup_environment_settings
62
75
  ThinkingSphinx.remote_sphinx = true
63
-
76
+
64
77
  set_searchd_settings
65
78
  set_indexer_settings
66
79
  set_path_settings
67
80
  end
68
-
81
+
69
82
  def set_path_settings
70
83
  thinking_sphinx.searchd_file_path = "#{base_path}/indexes"
71
-
84
+
72
85
  riddle.searchd.pid_file = "#{base_path}/searchd.pid"
73
86
  riddle.searchd.log = "#{log_path}/searchd.log"
74
87
  riddle.searchd.query_log = "#{log_path}/searchd.query.log"
75
88
  end
76
-
89
+
77
90
  def set_searchd_settings
78
91
  thinking_sphinx.port = port
79
92
  thinking_sphinx.address = host
93
+
94
+ if riddle.searchd.respond_to?(:client_key)
95
+ riddle.searchd.client_key = client_key
96
+ end
80
97
  end
81
-
98
+
82
99
  def set_indexer_settings
83
100
  riddle.indexer.mem_limit = mem_limit.to_s + 'M'
84
101
  end
85
-
102
+
86
103
  def set_database_settings
104
+ return unless FlyingSphinx::Tunnel.required?
105
+
87
106
  riddle.indexes.each do |index|
88
107
  next unless index.respond_to?(:sources)
89
-
108
+
90
109
  index.sources.each do |source|
91
110
  source.sql_host = '127.0.0.1'
92
111
  source.sql_port = database_port
93
112
  end
94
113
  end
95
114
  end
96
-
115
+
97
116
  def identifier_from_env
98
117
  ENV['FLYING_SPHINX_IDENTIFIER']
99
118
  end
100
-
119
+
101
120
  def api_key_from_env
102
121
  ENV['FLYING_SPHINX_API_KEY']
103
122
  end
123
+
124
+ def host_from_env
125
+ ENV['FLYING_SPHINX_HOST']
126
+ end
127
+
128
+ def port_from_env
129
+ ENV['FLYING_SPHINX_PORT']
130
+ end
104
131
  end
@@ -12,7 +12,11 @@ class FlyingSphinx::DelayedDelta < ThinkingSphinx::Deltas::DefaultDelta
12
12
  def self.enqueue(object, priority = 0)
13
13
  return if duplicates_exist? object
14
14
 
15
- ::Delayed::Job.enqueue(object, :priority => priority)
15
+ if defined?(Rails) && Rails.version.to_i <= 2
16
+ ::Delayed::Job.enqueue(object, priority)
17
+ else
18
+ ::Delayed::Job.enqueue(object, :priority => priority)
19
+ end
16
20
  end
17
21
 
18
22
  # Checks whether a given job already exists in the queue.
@@ -1,67 +1,107 @@
1
1
  class FlyingSphinx::IndexRequest
2
2
  attr_reader :index_id, :indices
3
-
3
+
4
+ INDEX_COMPLETE_CHECKING_INTERVAL = 3
5
+
4
6
  # Remove all Delta jobs from the queue. If the
5
7
  # delayed_jobs table does not exist, this method will do nothing.
6
- #
8
+ #
7
9
  def self.cancel_jobs
8
10
  return unless defined?(::Delayed) && ::Delayed::Job.table_exists?
9
-
11
+
10
12
  ::Delayed::Job.delete_all "handler LIKE '--- !ruby/object:FlyingSphinx::%'"
11
13
  end
12
-
14
+
15
+ def self.output_last_index
16
+ index = FlyingSphinx::Configuration.new.api.get('indices/last').body
17
+ puts "Index Job Status: #{index.status}"
18
+ puts "Index Log:\n#{index.log}"
19
+ end
20
+
13
21
  def initialize(indices = [])
14
22
  @indices = indices
15
23
  end
16
-
24
+
17
25
  # Shows index name in Delayed::Job#name.
18
- #
26
+ #
19
27
  def display_name
20
28
  "#{self.class.name} for #{indices.join(', ')}"
21
29
  end
22
-
30
+
23
31
  def update_and_index
24
32
  update_sphinx_configuration
25
33
  index
26
34
  end
27
35
 
36
+ def status_message
37
+ status = request_status
38
+ case status
39
+ when 'FINISHED'
40
+ 'Index Request has completed.'
41
+ when 'FAILED'
42
+ 'Index Request failed.'
43
+ when 'PENDING'
44
+ 'Index Request is still pending - something has gone wrong.'
45
+ else
46
+ "Unknown index response: '#{status}'."
47
+ end
48
+ end
49
+
28
50
  # Runs Sphinx's indexer tool to process the index. Currently assumes Sphinx is
29
51
  # running.
30
- #
52
+ #
31
53
  # @return [Boolean] true
32
- #
54
+ #
33
55
  def perform
34
56
  index
35
57
  true
36
58
  end
37
-
59
+
38
60
  private
39
-
61
+
40
62
  def configuration
41
63
  @configuration ||= FlyingSphinx::Configuration.new
42
64
  end
43
-
65
+
44
66
  def update_sphinx_configuration
45
- api.put '/app', :configuration => configuration.sphinx_configuration
67
+ api.put('/', :configuration => configuration.sphinx_configuration)
46
68
  end
47
-
69
+
48
70
  def index
49
- FlyingSphinx::Tunnel.connect(configuration) do
50
- begin_request unless request_begun?
51
-
52
- !request_complete?
71
+ if FlyingSphinx::Tunnel.required?
72
+ tunnelled_index
73
+ else
74
+ direct_index
53
75
  end
54
76
  rescue Net::SSH::Exception
55
- cancel_request
77
+ # Server closed the connection on us. That's (hopefully) expected, nothing
78
+ # to worry about.
56
79
  rescue RuntimeError => err
57
80
  puts err.message
58
81
  end
59
82
 
83
+ def tunnelled_index
84
+ FlyingSphinx::Tunnel.connect(configuration) do
85
+ begin_request unless request_begun?
86
+
87
+ true
88
+ end
89
+ end
90
+
91
+ def direct_index
92
+ begin_request
93
+ while !request_complete?
94
+ sleep 1
95
+ end
96
+ end
97
+
60
98
  def begin_request
61
- @index_id = api.post('/app/indices', :indices => indices.join(','))
99
+ response = api.post 'indices', :indices => indices.join(',')
100
+
101
+ @index_id = response.body.id
62
102
  @request_begun = true
63
-
64
- raise RuntimeError, 'Your account does not support delta indexing. Upgrading plans is probably the best way around this.' if @index_id == 'BLOCKED'
103
+
104
+ raise RuntimeError, 'Your account does not support delta indexing. Upgrading plans is probably the best way around this.' if response.body.status == 'BLOCKED'
65
105
  end
66
106
 
67
107
  def request_begun?
@@ -69,10 +109,8 @@ class FlyingSphinx::IndexRequest
69
109
  end
70
110
 
71
111
  def request_complete?
72
- response = api.get("/app/indices/#{index_id}")
73
- case response.body
112
+ case request_status
74
113
  when 'FINISHED', 'FAILED'
75
- puts "Indexing request failed." if response.body == 'FAILED'
76
114
  true
77
115
  when 'PENDING'
78
116
  false
@@ -81,14 +119,19 @@ class FlyingSphinx::IndexRequest
81
119
  end
82
120
  end
83
121
 
122
+ def request_status
123
+ api.get("indices/#{index_id}").body.status
124
+ end
125
+
84
126
  def cancel_request
85
127
  return if index_id.nil?
86
-
128
+
87
129
  puts "Connecting Flying Sphinx to the Database failed"
88
130
  puts "Cancelling Index Request..."
89
- api.put "/app/indices/#{index_id}", :status => 'CANCELLED'
131
+
132
+ api.put("indices/#{index_id}", :status => 'CANCELLED')
90
133
  end
91
-
134
+
92
135
  def api
93
136
  configuration.api
94
137
  end