bits-installer 0.1.1 → 0.3.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.
@@ -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