bits-installer 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,6 +4,7 @@ require 'bits/command_provider'
4
4
  require 'bits/package'
5
5
  require 'bits/logging'
6
6
  require 'bits/spawn'
7
+ require 'bits/external_interface'
7
8
 
8
9
  require 'json'
9
10
 
@@ -34,8 +35,7 @@ module Bits
34
35
  true
35
36
  end
36
37
 
37
- def initialize(ns)
38
- super ns
38
+ def setup
39
39
  @client = interfaces[:python]
40
40
  end
41
41
 
@@ -5,6 +5,7 @@ require 'bits/spawn'
5
5
  require 'bits/package'
6
6
  require 'bits/logging'
7
7
  require 'bits/exceptions'
8
+ require 'bits/cache'
8
9
 
9
10
  require 'bits/external_interface'
10
11
 
@@ -18,6 +19,7 @@ module Bits
18
19
  include Bits::CommandProvider
19
20
  include Bits::ExternalInterface
20
21
  include Bits::ProviderReporting
22
+ include Bits::Cache
21
23
 
22
24
  PIP = 'pip'
23
25
  INDEX = 'https://pypi.python.org/pypi'
@@ -37,34 +39,38 @@ module Bits
37
39
  true
38
40
  end
39
41
 
40
- def initialize(ns)
41
- super ns
42
+ def setup
42
43
  @client = XMLRPC::Client.new_from_uri(INDEX)
43
44
  @client.http_header_extra = {'Content-Type' => 'text/xml'}
44
45
  @python = self.class.interfaces[:python]
46
+ @cache = setup_cache ns[:bits_dir], provider_id
45
47
  end
46
48
 
47
- def get_installed_version(package_atom)
48
- type, response = @python.request :python_info, :atom => package_atom
49
- return nil if type == :missing_atom
50
- response['installed']
51
- end
49
+ def sync
50
+ t = Time.new
52
51
 
53
- def get_candidate_version(package_atom)
54
- begin
55
- result = @client.call(:package_releases, package_atom)
56
- rescue SocketError
57
- return nil
52
+ sync_file = File.join ns[:bits_dir], "#{provider_id}.sync"
53
+ last_sync = get_last_sync sync_file
54
+
55
+ if last_sync.nil?
56
+ content = read_full
57
+ else
58
+ content = read_partial last_sync
58
59
  end
59
60
 
60
- raise MissingPackage.new package_atom if result.empty?
61
- return result[0]
61
+ File.open(sync_file, 'w') do |f|
62
+ f.puts t.to_i.to_s
63
+ end
64
+
65
+ @cache.set content
66
+ @cache.save
62
67
  end
63
68
 
64
69
  def query(package_atom)
65
70
  candidate = get_candidate_version package_atom
71
+ raise MissingPackage.new package_atom if candidate.nil?
66
72
  current = get_installed_version package_atom
67
- return Bits::Package.new(package_atom, current, candidate)
73
+ Bits::Package.new package_atom, current, candidate
68
74
  end
69
75
 
70
76
  def install(package)
@@ -82,5 +88,73 @@ module Bits
82
88
  end
83
89
  end
84
90
  end
91
+
92
+ private
93
+
94
+ def get_installed_version(package_atom)
95
+ type, response = @python.request :python_info, :atom => package_atom
96
+ return nil if type == :missing_atom
97
+ response['installed']
98
+ end
99
+
100
+ def get_candidate_version(package_atom)
101
+ begin
102
+ result = @client.call(:package_releases, package_atom)
103
+ rescue SocketError
104
+ return nil
105
+ end
106
+
107
+ return nil if result.empty?
108
+ return result[0]
109
+ end
110
+
111
+ # get the number from the specified file when we where last synced to
112
+ # upstream repository.
113
+ def get_last_sync(sync_file)
114
+ if File.file? sync_file
115
+ File.new(sync_file).read.to_i
116
+ else
117
+ nil
118
+ end
119
+ end
120
+
121
+ def read_partial(last_sync)
122
+ result = @client.call :changelog, last_sync
123
+
124
+ releases = Hash.new
125
+
126
+ result.each do |name, version, timestamp, purpose|
127
+ next if purpose != 'new release'
128
+
129
+ releases[name] = {
130
+ :atom => name,
131
+ :candidate => version,
132
+ }
133
+ end
134
+
135
+ releases
136
+ end
137
+
138
+ # TODO: works really slowly right now, fix it
139
+ def read_full
140
+ remote_packages = @client.call :list_packages
141
+
142
+ puts remote_packages.size
143
+
144
+ packages = Hash.new
145
+
146
+ remote_packages.each do |name|
147
+ candidate = get_candidate_version name
148
+
149
+ next if candidate.nil?
150
+
151
+ packages[name] = {
152
+ :atom => name,
153
+ :candidate => candidate,
154
+ }
155
+ end
156
+
157
+ packages
158
+ end
85
159
  end
86
160
  end
@@ -0,0 +1,88 @@
1
+ require 'bits/logging'
2
+ require 'bits/command_provider'
3
+ require 'bits/external_interface'
4
+ require 'bits/provider_reporting'
5
+ require 'bits/cache'
6
+
7
+ module Bits
8
+ define_provider :rubygems, \
9
+ :desc => "Provides interface for Rubygems" \
10
+ do
11
+ include Bits::Logging
12
+ include Bits::CommandProvider
13
+ include Bits::ExternalInterface
14
+ include Bits::ProviderReporting
15
+ include Bits::Cache
16
+
17
+ GEM = 'gem'
18
+
19
+ def self.check
20
+ unless self.setup_interface :ruby, :capabilities => [:rubygems]
21
+ check_error "Could not setup required interface"
22
+ return false
23
+ end
24
+
25
+ log.debug "rubygems is available"
26
+ true
27
+ end
28
+
29
+ def setup
30
+ @client = interfaces[:ruby]
31
+ @cache = setup_cache ns[:bits_dir], provider_id
32
+ end
33
+
34
+ def sync
35
+ type, response = @client.request :rubygems_candidates
36
+
37
+ unless type == :candidates
38
+ raise "Expected rubygems_candidate response but got: #{type}"
39
+ end
40
+
41
+ candidates = response['candidates']
42
+
43
+ log.info "Synced #{candidates.size} gems"
44
+
45
+ cache = candidates.inject({}){|h, i| h[i['atom']] = i; h}
46
+
47
+ @cache.set cache
48
+ @cache.save
49
+ end
50
+
51
+ def query(atom)
52
+ candidate = @cache[atom]
53
+
54
+ type, info = @client.request :rubygems_info, \
55
+ :package => atom, \
56
+ :remote => candidate.nil?
57
+
58
+ raise MissingPackage.new atom if type == :missing_package
59
+ raise "Expected info response but got: #{type}" unless type == :info
60
+
61
+ installed = info['installed']
62
+
63
+ candidate = if candidate.nil?
64
+ info['candidate']
65
+ else
66
+ candidate['version']
67
+ end
68
+
69
+ Bits::Package.new(atom, installed, candidate)
70
+ end
71
+
72
+ def install(package)
73
+ execute do
74
+ unless run [GEM, 'install', package.atom]
75
+ raise "Could not install package '#{package.atom}'"
76
+ end
77
+ end
78
+ end
79
+
80
+ def remove(package)
81
+ execute do
82
+ unless run [GEM, 'uninstall', package.atom]
83
+ raise "Could not remove package '#{package.atom}'"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -46,7 +46,7 @@ module Bits
46
46
  raise MissingProvidedPackage.new atom
47
47
  end
48
48
 
49
- return PackageProxy.new ppps, criteria
49
+ return PackageProxy.new atom, ppps, criteria
50
50
  end
51
51
 
52
52
  private
data/lib/bits/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Bits
2
- VERSION = '0.1.1'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -20,7 +20,7 @@ class RequestError(Exception):
20
20
  pass
21
21
 
22
22
 
23
- class MissingRequestKey(Exception):
23
+ class MissingRequestKey(RequestError):
24
24
  def __init__(self, key):
25
25
  super(MissingRequestKey, self).__init__(
26
26
  "Missing key '{0}' in request".format(key))
@@ -137,18 +137,18 @@ def guarded_handle_request(line):
137
137
 
138
138
 
139
139
  def main(args):
140
- while True:
141
- try:
140
+ try:
141
+ while True:
142
142
  line = sys.stdin.readline()
143
- except KeyboardInterrupt:
144
- break
145
143
 
146
- if not line:
147
- break
144
+ if not line:
145
+ break
148
146
 
149
- response = guarded_handle_request(line)
150
- print >>sys.stdout, json.dumps(response)
151
- sys.stdout.flush()
147
+ response = guarded_handle_request(line)
148
+ print >>sys.stdout, json.dumps(response)
149
+ sys.stdout.flush()
150
+ except KeyboardInterrupt:
151
+ pass
152
152
 
153
153
  return 0
154
154
 
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'json'
5
+
6
+ HAS_RUBYGEMS = true
7
+
8
+ $load_path = Array.new $:
9
+
10
+ HAS_HOMEBREW = begin
11
+ $: << '/usr/local/Library/Homebrew'
12
+
13
+ require 'global'
14
+ require 'formula'
15
+
16
+ true
17
+ rescue LoadError
18
+ $:.replace $load_path
19
+ false
20
+ end
21
+
22
+ class RequestError < Exception; end
23
+
24
+ class MissingRequestKey < RequestError
25
+ def initialize(key)
26
+ super "Missing key '#{key}' in request"
27
+ end
28
+ end
29
+
30
+ def ping_handler request
31
+ capabilities = []
32
+
33
+ capabilities << :homebrew if HAS_HOMEBREW
34
+ capabilities << :rubygems if HAS_RUBYGEMS
35
+
36
+ return {
37
+ :__type__ => "pong",
38
+ :capabilities => capabilities
39
+ }
40
+ end
41
+
42
+ def rubygems_info_handler request
43
+ remote = request['remote']
44
+ package = request['package']
45
+
46
+ if package.nil?
47
+ raise MissingRequestKey.new 'package'
48
+ end
49
+
50
+ candidate_version = if remote
51
+ rubygems_candidate_version package
52
+ else
53
+ nil
54
+ end
55
+
56
+ installed_version = rubygems_installed_version package
57
+
58
+ {
59
+ :__type__ => :info,
60
+ :installed => installed_version,
61
+ :candidate => candidate_version,
62
+ }
63
+ end
64
+
65
+ def rubygems_candidate_version package
66
+ dep = Gem::Dependency.new package
67
+ fetcher = Gem::SpecFetcher.fetcher
68
+ spec_tuples = fetcher.find_matching dep
69
+
70
+ if spec_tuples.empty?
71
+ return nil
72
+ end
73
+
74
+ spec, _ = spec_tuples[0]
75
+ _, version, _ = spec
76
+
77
+ version.version
78
+ end
79
+
80
+ def rubygems_installed_version package
81
+ result = Gem::Specification.find_all_by_name package
82
+ #result = Gem::Specification.source_index.find_name package
83
+ return nil if result.empty?
84
+ result.first.version.version
85
+ end
86
+
87
+ def iterate_candidates
88
+ fetcher = Gem::SpecFetcher.fetcher
89
+
90
+ fetcher.list.each do |source_uri, spec_tuples|
91
+ spec_tuples.each do |spec_tuple|
92
+ atom, version, _ = spec_tuple
93
+ yield atom, version.version
94
+ end
95
+ end
96
+ end
97
+
98
+ def rubygems_candidates_handler request
99
+ candidates = Array.new
100
+
101
+ iterate_candidates do |atom, version|
102
+ candidates << {
103
+ :atom => atom,
104
+ :version => version,
105
+ }
106
+ end
107
+
108
+ {
109
+ :__type__ => :candidates,
110
+ :candidates => candidates,
111
+ }
112
+ end
113
+
114
+ def homebrew_info_handler request
115
+ package = request['package']
116
+
117
+ if package.nil?
118
+ raise MissingRequestKey.new 'package'
119
+ end
120
+
121
+ installed, candidate = homebrew_get_version package
122
+
123
+ {
124
+ :__type__ => :info,
125
+ :installed => installed,
126
+ :candidate => candidate,
127
+ }
128
+ end
129
+
130
+ def homebrew_get_version(package)
131
+ begin
132
+ f = Formula.factory(package)
133
+ rescue FormulaUnavailableError
134
+ return [nil, nil]
135
+ end
136
+
137
+ [f.installed_version, f.version]
138
+ end
139
+
140
+ HANDLERS = {
141
+ :ping => :ping_handler,
142
+ :homebrew_info => :homebrew_info_handler,
143
+ :rubygems_info => :rubygems_info_handler,
144
+ :rubygems_candidates => :rubygems_candidates_handler,
145
+ }
146
+
147
+ def handle_request(line)
148
+ begin
149
+ request = JSON.load line
150
+ rescue
151
+ raise RequestError.new "Could not decode request, expected type: json"
152
+ end
153
+
154
+ request_type = request['__type__']
155
+
156
+ if request_type.nil?
157
+ raise RequestError.new "Missing 'type' in request"
158
+ end
159
+
160
+ request_type = request_type.to_sym
161
+
162
+ handler_name = HANDLERS[request_type]
163
+
164
+ if handler_name.nil?
165
+ raise RequestError.new "No handler for type '#{request_type}'"
166
+ end
167
+
168
+ begin
169
+ handler = method(handler_name)
170
+ rescue NameError
171
+ raise RequestError.new "No method for handler '#{request_type}'"
172
+ end
173
+
174
+ handler.call request
175
+ end
176
+
177
+ def guarded_handle_request(line)
178
+ handle_request line
179
+ rescue RequestError => e
180
+ return {
181
+ :__type__ => "error",
182
+ :text => e.to_s,
183
+ }
184
+ end
185
+
186
+ def main
187
+ $stdin.each do |line|
188
+ response = guarded_handle_request line
189
+ $stdout.puts JSON.dump response
190
+ $stdout.flush
191
+ end
192
+
193
+ return 0
194
+ rescue Interrupt
195
+ return 0
196
+ end
197
+
198
+ exit main