flying-sphinx 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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