summaryrefslogtreecommitdiff
path: root/lib/rubygems/source.rb
blob: 96d57870e2c3ba420bb88c6c8e43a711326c4f3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
require 'uri'
require 'fileutils'

class Gem::Source
  FILES = {
    :released   => 'specs',
    :latest     => 'latest_specs',
    :prerelease => 'prerelease_specs',
  }

  def initialize(uri)
    unless uri.kind_of? URI
      uri = URI.parse(uri.to_s)
    end

    @uri = uri
    @api_uri = nil
  end

  attr_reader :uri

  def api_uri
    require 'rubygems/remote_fetcher'
    @api_uri ||= Gem::RemoteFetcher.fetcher.api_endpoint uri
  end

  def <=>(other)
    if !@uri
      return 0 unless other.uri
      return -1
    end

    return 1 if !other.uri

    @uri.to_s <=> other.uri.to_s
  end

  include Comparable

  def ==(other)
    case other
    when self.class
      @uri == other.uri
    else
      false
    end
  end

  alias_method :eql?, :==

  def hash
    @uri.hash
  end

  ##
  # Returns the local directory to write +uri+ to.

  def cache_dir(uri)
    # Correct for windows paths
    escaped_path = uri.path.sub(/^\/([a-z]):\//i, '/\\1-/')
    root = File.join Gem.user_home, '.gem', 'specs'
    File.join root, "#{uri.host}%#{uri.port}", File.dirname(escaped_path)
  end

  def update_cache?
    @update_cache ||= File.stat(Gem.user_home).uid == Process.uid
  end

  def fetch_spec(name)
    fetcher = Gem::RemoteFetcher.fetcher

    spec_file_name = name.spec_name

    uri = @uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"

    cache_dir = cache_dir uri

    local_spec = File.join cache_dir, spec_file_name

    if File.exist? local_spec then
      spec = Gem.read_binary local_spec
      spec = Marshal.load(spec) rescue nil
      return spec if spec
    end

    uri.path << '.rz'

    spec = fetcher.fetch_path uri
    spec = Gem.inflate spec

    if update_cache? then
      FileUtils.mkdir_p cache_dir

      open local_spec, 'wb' do |io|
        io.write spec
      end
    end

    # TODO: Investigate setting Gem::Specification#loaded_from to a URI
    Marshal.load spec
  end

  ##
  # Loads +type+ kind of specs fetching from +@uri+ if the on-disk cache is
  # out of date.
  #
  # +type+ is one of the following:
  #
  # :released   => Return the list of all released specs
  # :latest     => Return the list of only the highest version of each gem
  # :prerelease => Return the list of all prerelease only specs
  #

  def load_specs(type)
    file       = FILES[type]
    fetcher    = Gem::RemoteFetcher.fetcher
    file_name  = "#{file}.#{Gem.marshal_version}"
    spec_path  = @uri + "#{file_name}.gz"
    cache_dir  = cache_dir spec_path
    local_file = File.join(cache_dir, file_name)
    retried    = false

    FileUtils.mkdir_p cache_dir if update_cache?

    spec_dump = fetcher.cache_update_path spec_path, local_file, update_cache?

    begin
      Gem::NameTuple.from_list Marshal.load(spec_dump)
    rescue ArgumentError
      if update_cache? && !retried
        FileUtils.rm local_file
        retried = true
        retry
      else
        raise Gem::Exception.new("Invalid spec cache file in #{local_file}")
      end
    end
  end

  def download(spec, dir=Dir.pwd)
    fetcher = Gem::RemoteFetcher.fetcher
    fetcher.download spec, @uri.to_s, dir
  end
end