smc-get 0.1.0 → 0.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +674 -674
- data/HISTORY.rdoc +31 -0
- data/README.rdoc +163 -148
- data/VERSION.txt +1 -1
- data/bin/smc-checksum +44 -0
- data/bin/smc-get +31 -31
- data/config/smc-get.yml +32 -32
- data/lib/smc_get.rb +23 -21
- data/lib/smc_get/cui.rb +325 -289
- data/lib/smc_get/cui_commands/build.rb +329 -0
- data/lib/smc_get/cui_commands/command.rb +117 -81
- data/lib/smc_get/cui_commands/getinfo.rb +92 -91
- data/lib/smc_get/cui_commands/help.rb +65 -64
- data/lib/smc_get/cui_commands/install.rb +101 -91
- data/lib/smc_get/cui_commands/list.rb +101 -80
- data/lib/smc_get/cui_commands/search.rb +106 -109
- data/lib/smc_get/cui_commands/uninstall.rb +80 -78
- data/lib/smc_get/cui_commands/update.rb +119 -0
- data/lib/smc_get/cui_commands/version.rb +58 -57
- data/lib/smc_get/errors.rb +140 -113
- data/lib/smc_get/gui.rb +20 -19
- data/lib/smc_get/local_repository.rb +277 -0
- data/lib/smc_get/package.rb +260 -279
- data/lib/smc_get/package_archive.rb +80 -0
- data/lib/smc_get/package_specification.rb +253 -0
- data/lib/smc_get/remote_repository.rb +272 -0
- data/lib/smc_get/repository.rb +10 -0
- data/lib/smc_get/smc_get.rb +117 -139
- data/smcpak.rdoc +332 -0
- data/test/test_smc-get-cui.rb +123 -121
- data/test/test_smc-get-lib.rb +172 -170
- metadata +71 -36
@@ -0,0 +1,80 @@
|
|
1
|
+
#Encoding: UTF-8
|
2
|
+
################################################################################
|
3
|
+
# This file is part of smc-get.
|
4
|
+
# Copyright (C) 2010-2011 Entertaining Software, Inc.
|
5
|
+
# Copyright (C) 2011 Marvin Gülker
|
6
|
+
#
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
################################################################################
|
20
|
+
|
21
|
+
module SmcGet
|
22
|
+
|
23
|
+
#This class allows for easy compression and decompression of .smcpak
|
24
|
+
#files. Please note that during this process no validations are performed,
|
25
|
+
#i.e. you have to ensure that the directory you want to compress is in
|
26
|
+
#smc package layout and the .smcpak files you want to decompress are
|
27
|
+
#really smc packages.
|
28
|
+
class PackageArchive
|
29
|
+
|
30
|
+
#The location of this archive.
|
31
|
+
attr_reader :path
|
32
|
+
|
33
|
+
#Compresses all files in +directory+ into a TAR.XZ file with the
|
34
|
+
#extension ".smcpak".
|
35
|
+
#Returns an object of this class.
|
36
|
+
# smcpak = PackageArchive.compress("mypackage")
|
37
|
+
# puts smcpak.path #=> mypackage.smcpak
|
38
|
+
def self.compress(directory, goal_file)
|
39
|
+
directory = Pathname.new(directory).expand_path
|
40
|
+
tar_file = Pathname.new("#{goal_file}.tar").expand_path
|
41
|
+
xz_file = Pathname.new(goal_file).expand_path
|
42
|
+
|
43
|
+
Dir.chdir(directory.parent) do
|
44
|
+
tar_file.open("wb") do |file|
|
45
|
+
Archive::Tar::Minitar.pack(directory.relative_path_from(Pathname.new(".").expand_path).to_s, file)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
XZ.compress_file(tar_file, xz_file)
|
49
|
+
|
50
|
+
tar_file.delete #We don’t need it anymore
|
51
|
+
|
52
|
+
new(xz_file)
|
53
|
+
end
|
54
|
+
|
55
|
+
#Creates a new PackageArchive from an existing .smcpak file.
|
56
|
+
def initialize(archive)
|
57
|
+
@path = Pathname.new(archive)
|
58
|
+
end
|
59
|
+
|
60
|
+
#Decompresses this archive into +directory+, creating a subdirectory
|
61
|
+
#named after the archive without the extension, and returns the path
|
62
|
+
#to that subdirectory (a Pathname object).
|
63
|
+
# smcpak = PackageArchive.new("mypackage.smcpak")
|
64
|
+
# puts smcpak.decompress #=> #<Pathname:mypackage>
|
65
|
+
def decompress(directory)
|
66
|
+
directory = Pathname.new(directory)
|
67
|
+
tar_file = directory + @path.basename.to_s.sub(/\.smcpak$/, ".tar")
|
68
|
+
dest_dir = directory + tar_file.basename.to_s.sub(/\.tar$/, "")
|
69
|
+
|
70
|
+
XZ.decompress_file(@path.to_s, tar_file.to_s)
|
71
|
+
Archive::Tar::Minitar.unpack(tar_file.to_s, dest_dir.to_s)
|
72
|
+
|
73
|
+
tar_file.delete #We don’ŧ need it anymore
|
74
|
+
|
75
|
+
dest_dir
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
#Encoding: UTF-8
|
2
|
+
################################################################################
|
3
|
+
# This file is part of smc-get.
|
4
|
+
# Copyright (C) 2010-2011 Entertaining Software, Inc.
|
5
|
+
# Copyright (C) 2011 Marvin Gülker
|
6
|
+
#
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
################################################################################
|
20
|
+
|
21
|
+
module SmcGet
|
22
|
+
|
23
|
+
#A PackageSpecification object is a mostly informational object
|
24
|
+
#directly related to Package objects. That is, each and every
|
25
|
+
#Package instance has a Package#spec method that will retrieve
|
26
|
+
#the PackageSpecification, an instance of this class, for you:
|
27
|
+
#
|
28
|
+
# puts pkg.spec.title #=> Cool package
|
29
|
+
#
|
30
|
+
#Instances of this class can be understood as the parsed content of
|
31
|
+
#a package’s specification file. It has getter and setter methods
|
32
|
+
#reflecting the keys that are allowed in the specification (see
|
33
|
+
#the smcpak.rdoc file), but unless you want to build smc packages,
|
34
|
+
#there’s no need for you to use the setter methods.
|
35
|
+
class PackageSpecification
|
36
|
+
|
37
|
+
#The keys listed here must be mentioned inside a package spec,
|
38
|
+
#otherwise the package is considered broken.
|
39
|
+
SPEC_MANDATORY_KEYS = [:title, :last_update, :authors, :difficulty, :description, :checksums].freeze
|
40
|
+
|
41
|
+
##
|
42
|
+
# :attr_accessor: title
|
43
|
+
#The package’s title.
|
44
|
+
|
45
|
+
##
|
46
|
+
# :attr_accessor: last_update
|
47
|
+
#The time (an instance of class Time) indicating when the package
|
48
|
+
#was last updated.
|
49
|
+
|
50
|
+
##
|
51
|
+
# :attr_accessor: authors
|
52
|
+
#The authors of this package. An array.
|
53
|
+
|
54
|
+
##
|
55
|
+
# :attr_accessor: difficulty
|
56
|
+
#The difficulty of this package as a string.
|
57
|
+
|
58
|
+
##
|
59
|
+
# :attr_accessor: description
|
60
|
+
#The description of this package.
|
61
|
+
|
62
|
+
##
|
63
|
+
# :attr_accessor: install_message
|
64
|
+
#A message to display during installation of this package or nil if
|
65
|
+
#no message shall be displayed.
|
66
|
+
|
67
|
+
##
|
68
|
+
# :attr_accessor: remove_message
|
69
|
+
#A message to display during removing of this package or nil if no
|
70
|
+
#message shall be displayed.
|
71
|
+
|
72
|
+
##
|
73
|
+
# :attr_accessor: dependecies
|
74
|
+
#An array of package names this package depends on, i.e. packages that
|
75
|
+
#need to be installed before this one can be installed. The array is
|
76
|
+
#empty if no dependecies exist.
|
77
|
+
|
78
|
+
##
|
79
|
+
# :attr_accessor: levels
|
80
|
+
#An array of level file names (strings).
|
81
|
+
|
82
|
+
##
|
83
|
+
# :attr_accessor: music
|
84
|
+
#An array of music file names (strings).
|
85
|
+
|
86
|
+
##
|
87
|
+
# :attr_accessor: sounds
|
88
|
+
#An array of sound file names (strings).
|
89
|
+
|
90
|
+
##
|
91
|
+
# :attr_accessor: graphics
|
92
|
+
#An array of graphic file names (strings).
|
93
|
+
|
94
|
+
##
|
95
|
+
# :attr_accessor: worlds
|
96
|
+
#An array of graphic file names (strings).
|
97
|
+
|
98
|
+
##
|
99
|
+
# :attr_accessor: checksums
|
100
|
+
#A hash that maps each filename in this package to it’s SHA1 checksum.
|
101
|
+
|
102
|
+
#The name of the package this specification is used in, without any
|
103
|
+
#file extension.
|
104
|
+
attr_reader :name
|
105
|
+
|
106
|
+
##
|
107
|
+
# :attr_reader: compressed_file_name
|
108
|
+
#The name of the compressed file this specification should belong to.
|
109
|
+
#The same as name, but the extension .smcpak was appended.
|
110
|
+
|
111
|
+
##
|
112
|
+
# :attr_reader: spec_file_name
|
113
|
+
#The name of the specification file. The same as name, but
|
114
|
+
#the extension .yml was appended.
|
115
|
+
|
116
|
+
#Creates a PackageSpecification by directly reading a complete
|
117
|
+
#spec from a YAML file.
|
118
|
+
#==Parameter
|
119
|
+
#[path] The path to the file to read.
|
120
|
+
#==Return value
|
121
|
+
#An instance of this class.
|
122
|
+
#==Raises
|
123
|
+
#[InvalidSpecification] +path+ was not found or was malformatted.
|
124
|
+
#==Example
|
125
|
+
# path = remote_repo.fetch_spec("cool_pkg.yml")
|
126
|
+
# spec = SmcGet::PackageSpecification.from_file(path)
|
127
|
+
# puts spec.title #=> "Cool package"
|
128
|
+
#==Remarks
|
129
|
+
#This method may be useful if you don’t need a full-blown
|
130
|
+
#Package object and just want to deal with it’s most important
|
131
|
+
#attributes.
|
132
|
+
def self.from_file(path)
|
133
|
+
info = nil
|
134
|
+
begin
|
135
|
+
info = YAML.load_file(path.to_s)
|
136
|
+
rescue Errno::ENOENT => e
|
137
|
+
raise(Errors::InvalidSpecification, "File '#{path}' doesn't exist!")
|
138
|
+
rescue => e
|
139
|
+
raise(Errors::InvalidSpecification, "Invalid YAML: #{e.message}")
|
140
|
+
end
|
141
|
+
|
142
|
+
spec = new(File.basename(path).sub(/\.yml$/, ""))
|
143
|
+
info.each_pair do |key, value|
|
144
|
+
spec.send(:"#{key}=", value)
|
145
|
+
end
|
146
|
+
#TODO: Convert the strings in :checksums to strings, except the
|
147
|
+
#filenames, those should be strings. Anyone???
|
148
|
+
|
149
|
+
raise(Errors::InvalidSpecification, spec.validate.first) unless spec.valid?
|
150
|
+
|
151
|
+
spec
|
152
|
+
end
|
153
|
+
|
154
|
+
#Returns the matching package name from the package specification’s name
|
155
|
+
#by replacing the .yml extension with .smcpak.
|
156
|
+
#==Return value
|
157
|
+
#A string ending in ".smcpak".
|
158
|
+
#==Example
|
159
|
+
# p SmcGet::PackageSpecification.spec2pkg("cool_pkg.yml") #=> "cool_pkg.smcpak")
|
160
|
+
def self.spec2pkg(spec_file_name) # :nodoc:
|
161
|
+
spec_file_name.to_s.sub(/\.yml$/, ".smcpak")
|
162
|
+
end
|
163
|
+
|
164
|
+
#Returns the matching specification file name from the package’s name
|
165
|
+
#by replacing the .smcpak extension with .yml.
|
166
|
+
#==Return value
|
167
|
+
#A string ending in ".yml"
|
168
|
+
#==Example
|
169
|
+
# p SmcGet::PackageSpecification.pkg2spec("cool_pkg.smcpak") #=> "cool_pkg.yml"
|
170
|
+
def self.pkg2spec(package_file_name) # :nodoc:
|
171
|
+
package_file_name.to_s.sub(/\.smcpak$/, ".yml")
|
172
|
+
end
|
173
|
+
|
174
|
+
def initialize(pkg_name)
|
175
|
+
@info = {:dependencies => [], :levels => [], :music => [], :sounds => [], :graphics => [], :worlds => []}
|
176
|
+
@name = pkg_name
|
177
|
+
end
|
178
|
+
|
179
|
+
#See attribute.
|
180
|
+
def compressed_file_name # :nodoc:
|
181
|
+
"#@name.smcpak"
|
182
|
+
end
|
183
|
+
|
184
|
+
#See attribute.
|
185
|
+
def spec_file_name # :nodoc:
|
186
|
+
"#@name.yml"
|
187
|
+
end
|
188
|
+
|
189
|
+
[:title, :last_update, :authors, :difficulty, :description, :install_message, :remove_message, :dependencies, :levels, :music, :sounds, :graphics, :worlds, :checksums].each do |sym|
|
190
|
+
define_method(sym){@info[sym]}
|
191
|
+
define_method(:"#{sym}="){|val| @info[sym] = val}
|
192
|
+
end
|
193
|
+
|
194
|
+
def [](sym)
|
195
|
+
if respond_to?(sym)
|
196
|
+
send(sym)
|
197
|
+
else
|
198
|
+
raise(IndexError, "No such specification key: #{sym}!")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def valid?
|
203
|
+
validate.empty?
|
204
|
+
end
|
205
|
+
|
206
|
+
def validate
|
207
|
+
errors = []
|
208
|
+
|
209
|
+
SPEC_MANDATORY_KEYS.each do |sym|
|
210
|
+
errors << "Mandatory key #{sym} is missing!" unless @info.has_key?(sym)
|
211
|
+
end
|
212
|
+
|
213
|
+
errors
|
214
|
+
end
|
215
|
+
|
216
|
+
#Compares two specifications. They are considered equal if all their
|
217
|
+
#attributes (levels, difficulty, etc.) are equal.
|
218
|
+
def ==(other)
|
219
|
+
return false unless other.respond_to? :info
|
220
|
+
@info == other.info
|
221
|
+
end
|
222
|
+
|
223
|
+
#Saves the package specification in YAML format into a file.
|
224
|
+
#==Parameter
|
225
|
+
#[directory] The directory where to save the file to. The filename is automatically
|
226
|
+
# detected from the attributes set for the specification.
|
227
|
+
#==Raises
|
228
|
+
#[InvalidSpecification] The specification was not valid, i.e. contained incorrect
|
229
|
+
# or missing values.
|
230
|
+
#==Example
|
231
|
+
# p spec.name #=> "cool_pkg"
|
232
|
+
# spec.save(".")
|
233
|
+
# p File.file?("cool_pkg.yml") #=> true
|
234
|
+
def save(directory)
|
235
|
+
raise(Errors::InvalidSpecification, validate.first) unless valid?
|
236
|
+
|
237
|
+
path = Pathname.new(directory) + "#{@name}.yml"
|
238
|
+
#Turn the spec keys for serialization into strings
|
239
|
+
hsh = {}
|
240
|
+
@info.each_pair{|k, v| hsh[k.to_s] = v}
|
241
|
+
path.open("w"){|f| YAML.dump(hsh, f)}
|
242
|
+
end
|
243
|
+
|
244
|
+
protected
|
245
|
+
|
246
|
+
#Returns the complete internal information hash.
|
247
|
+
def info
|
248
|
+
@info
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
#Encoding: UTF-8
|
2
|
+
################################################################################
|
3
|
+
# This file is part of smc-get.
|
4
|
+
# Copyright (C) 2010-2011 Entertaining Software, Inc.
|
5
|
+
# Copyright (C) 2011 Marvin Gülker
|
6
|
+
#
|
7
|
+
# This program is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
################################################################################
|
20
|
+
|
21
|
+
module SmcGet
|
22
|
+
|
23
|
+
#A RemoteRepository represents a web location from which <tt>smc-get</tt>
|
24
|
+
#is able to download packages. The structure of those repositories is
|
25
|
+
#explained in the smcpak.rdoc file in the <i>Repostories</i> section.
|
26
|
+
class RemoteRepository < Repository
|
27
|
+
|
28
|
+
#Name of the remote directory containing the <tt>.smcpak</tt> files.
|
29
|
+
PACKAGES_DIR = "packages"
|
30
|
+
#Name of the remote directory containing the packages specifications.
|
31
|
+
SPECS_DIR = "specs"
|
32
|
+
#Name of the file containg the names of all packages in this repository.
|
33
|
+
LIST_FILE = "packages.lst"
|
34
|
+
#Number of bytes to read while downloading a file.
|
35
|
+
CHUNK_SIZE = 1024
|
36
|
+
|
37
|
+
##
|
38
|
+
# :attr_reader: packages
|
39
|
+
#Returns an array of remote Packages that contains all packages
|
40
|
+
#in this repository. This is a very time-intensive operation, depending
|
41
|
+
#on how many packages the repository contains, because it downloads all
|
42
|
+
#the package specifications of all the packages. It’s not a good idea
|
43
|
+
#to call this.
|
44
|
+
|
45
|
+
#An URI object representing the repository’s URI.
|
46
|
+
attr_reader :uri
|
47
|
+
#A list of all package names (without an extension).
|
48
|
+
attr_reader :packages_list
|
49
|
+
|
50
|
+
#Creates a "new" Repository.
|
51
|
+
#==Parameters
|
52
|
+
#[uri] The URI of the repository, something like
|
53
|
+
# "\http://my-host.org/smc-repo".
|
54
|
+
#==Raises
|
55
|
+
#[InvalidRepository] The repository is either nonexistant or doesn’t obey
|
56
|
+
# the repository structure format.
|
57
|
+
#==Return value
|
58
|
+
#The newly created repository.
|
59
|
+
#==Usage
|
60
|
+
# r = Repository.new("http://myrepo.org")
|
61
|
+
# r = Repository.new("https://my-host.org/smc-repo")
|
62
|
+
# r = Repository.new("ftp://ftp.my-host.org/smc")
|
63
|
+
def initialize(uri)
|
64
|
+
@uri = URI.parse(uri)
|
65
|
+
#Download the packages list. Usually it’s small enough to fit into RAM.
|
66
|
+
begin
|
67
|
+
@packages_list = open(@uri + LIST_FILE){|tmpfile| tmpfile.read}.split
|
68
|
+
rescue SocketError, Errno::ECONNREFUSED, OpenURI::HTTPError => e #open-uri raises HTTPError even in case of other protocols
|
69
|
+
raise(Errors::InvalidRepository.new(@uri), e.message)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#Downloads the given package specification from this repository and
|
74
|
+
#places it in +directory+.
|
75
|
+
#
|
76
|
+
#If the file already exists, it’s overwritten.
|
77
|
+
#==Parameters
|
78
|
+
#[spec_file] The package specification file you want to download. It must
|
79
|
+
# end with the <tt>.yml</tt> extension.
|
80
|
+
#[directory] (".") The directory where you want to download the file to.
|
81
|
+
# Created if it doesn’t exist.
|
82
|
+
#==Raises
|
83
|
+
#[NoSuchResourceError] The package name for this specification couldn’t be
|
84
|
+
# found in the repository. Note that the spec may be
|
85
|
+
# there despite of this, but packages not listed
|
86
|
+
# in the repository’s contents file are treated as if
|
87
|
+
# they weren’t there.
|
88
|
+
#==Return value
|
89
|
+
#The path to the downloaded file as a Pathname object.
|
90
|
+
#==Usage
|
91
|
+
# my_repo.fetch_spec("mypackage.yml", "/home/freak/Downloads")
|
92
|
+
#
|
93
|
+
# my_repo.fetch_spec("mypackage.yml")
|
94
|
+
def fetch_spec(spec_file, directory = ".")
|
95
|
+
directory = Pathname.new(directory)
|
96
|
+
pkg_name = spec_file.sub(/\.yml$/, "")
|
97
|
+
goal_file = directory + spec_file
|
98
|
+
|
99
|
+
unless @packages_list.include?(pkg_name)
|
100
|
+
raise(Errors::NoSuchResourceError.new(:spec, spec_file), "Package '#{pkg_name}' not found in the repository!")
|
101
|
+
end
|
102
|
+
|
103
|
+
directory.mktree unless directory.directory?
|
104
|
+
|
105
|
+
#I am quite sure that URI#merge has a bug. Example:
|
106
|
+
# uri = URI.parse("http://www.ruby-lang.org")
|
107
|
+
#Now try to append the path test/test2:
|
108
|
+
# uri2 = uri + "test" + "test2"
|
109
|
+
#What do you think contains uri2? This:
|
110
|
+
# http://www.ruby-lang.org/test2
|
111
|
+
#Something missing, eh? Even more surprising, this one works as
|
112
|
+
#expected:
|
113
|
+
# uri + "test/test2"
|
114
|
+
#The second one is the workaround I use in the following line.
|
115
|
+
open(@uri.merge("#{SPECS_DIR}/#{spec_file}")) do |tempfile|
|
116
|
+
File.open(goal_file, "w") do |file|
|
117
|
+
file.write(tempfile.read) #Specs almost ever are small enough to fit in RAM
|
118
|
+
end
|
119
|
+
end
|
120
|
+
goal_file
|
121
|
+
end
|
122
|
+
|
123
|
+
#Downloads the given package from this repository and places it in
|
124
|
+
#+directory+. Yields the package’s total size in bytes and how many
|
125
|
+
#bytes have already been downloaded. If the size is unknown for some
|
126
|
+
#reason, +bytes_total+ is nil. For the last block call, +bytes_total+
|
127
|
+
#and +bytes_done+ are guaranteed to be equal, except if +bytes_total+
|
128
|
+
#couldn’t be determined in which case it’s still nil.
|
129
|
+
#
|
130
|
+
#If the file does already exist in +directory+, it is overwritten.
|
131
|
+
#==Parameters
|
132
|
+
#[pkg_file] The package file you want to download. It must end in
|
133
|
+
# the <tt>.smcpak</tt> extension.
|
134
|
+
#[directory] (".") The directory where you want to download the file to.
|
135
|
+
# Created if it doesn’t exist.
|
136
|
+
#==Raises
|
137
|
+
#[NoSuchPackageError] The package name for this package couldn’t be
|
138
|
+
# found in the repository. Note that the package may be
|
139
|
+
# there despite of this, but packages not listed
|
140
|
+
# in the repository’s contents file are treated as if
|
141
|
+
# they weren’t there.
|
142
|
+
#[OpenURI::HTTPError] Connection error.
|
143
|
+
#==Return value
|
144
|
+
#The path to the downloaded file as a Pathname object.
|
145
|
+
#==Usage
|
146
|
+
# my_repo.fetch_package("mypackage.smcpak", "/home/freak/downloads")
|
147
|
+
#
|
148
|
+
# my_repo.fetch_package("mypackage.smcpak") do |bytes_total, bytes_done|
|
149
|
+
# print("\rDownloaded #{bytes_done} bytes of #{bytes_total}.") if bytes_total
|
150
|
+
# end
|
151
|
+
def fetch_package(pkg_file, directory = ".")
|
152
|
+
directory = Pathname.new(directory)
|
153
|
+
pkg_name = pkg_file.sub(/\.smcpak$/, "")
|
154
|
+
goal_file = directory + pkg_file
|
155
|
+
|
156
|
+
unless @packages_list.include?(pkg_name)
|
157
|
+
raise(Errors::NoSuchPackageError.new(pkg_name), "ERROR: Package '#{pkg_name}' not found in the repository!")
|
158
|
+
end
|
159
|
+
|
160
|
+
directory.mktree unless directory.directory?
|
161
|
+
|
162
|
+
bytes_total = nil
|
163
|
+
size_proc = lambda{|content_length| bytes_total = content_length}
|
164
|
+
prog_proc = lambda{|bytes_done| yield(bytes_total, bytes_done)}
|
165
|
+
|
166
|
+
#See the source of #fetch_spec for an explanation on the obscure
|
167
|
+
#URI concatenation.
|
168
|
+
open(@uri + "#{PACKAGES_DIR}/#{pkg_file}", "rb", content_length_proc: size_proc, progress_proc: prog_proc) do |tempfile|
|
169
|
+
#The packages may be too big for fitting into RAM, therefore we’re going
|
170
|
+
#to read and write the packages chunk by chunk. Btw. please notice me
|
171
|
+
#if you find a SMC package that’s larger than 4 GiB! I’d be curious
|
172
|
+
#about what it contains!
|
173
|
+
File.open(goal_file, "wb") do |file|
|
174
|
+
while chunk = tempfile.read(CHUNK_SIZE)
|
175
|
+
file.write(chunk)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
goal_file
|
181
|
+
end
|
182
|
+
|
183
|
+
#Not implemented yet.
|
184
|
+
def install(package, &block)
|
185
|
+
raise(NotImplementedError, "Can't automatically upload to remote repositories yet!")
|
186
|
+
end
|
187
|
+
|
188
|
+
#Not implemented yet.
|
189
|
+
def uninstall(pkg_name)
|
190
|
+
raise(NotImplementedError, "Can't automatically remove from remote repositories yet!")
|
191
|
+
end
|
192
|
+
|
193
|
+
#Returns the URI of the remote repository.
|
194
|
+
def to_s
|
195
|
+
@uri.to_s
|
196
|
+
end
|
197
|
+
|
198
|
+
#See attribute.
|
199
|
+
# def packages # :nodoc:
|
200
|
+
# @packages_list.map do |pkg_name|
|
201
|
+
# Package.from_repository(self, "#{pkg_name}.yml")
|
202
|
+
# end
|
203
|
+
# end
|
204
|
+
|
205
|
+
#True if a package with the given name (without the .smcpak extension)
|
206
|
+
#exists in the repository.
|
207
|
+
def contain?(pkg)
|
208
|
+
if pkg.kind_of? Package
|
209
|
+
@packages_list.include?(pkg.spec.name)
|
210
|
+
else
|
211
|
+
@packages_list.include?(pkg_name)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
alias contains? contain?
|
215
|
+
|
216
|
+
#call-seq:
|
217
|
+
# search(regexp [, *attributes ]){|pkgname|...}
|
218
|
+
#
|
219
|
+
#Searches for a specific package and yields each candidate to the
|
220
|
+
#given block.
|
221
|
+
#==Parameters
|
222
|
+
#[regexp] The Regular Expression to use as the search pattern.
|
223
|
+
#[*attributes] (<tt>[:name]</tt>) A list of all attributes to match
|
224
|
+
# the Regular Expression against (note that only
|
225
|
+
# passing :name is siginificantly faster, because
|
226
|
+
# there’s no need to download the specs).
|
227
|
+
# Possible attributes:
|
228
|
+
# * name
|
229
|
+
# * title
|
230
|
+
# * authors
|
231
|
+
# * difficulty
|
232
|
+
# * description
|
233
|
+
# * levels
|
234
|
+
# * music
|
235
|
+
# * sounds
|
236
|
+
# * graphics
|
237
|
+
# * worlds
|
238
|
+
#[pkgname] *Blockargument*. The package name (not title!) of
|
239
|
+
# a package matching the search criteria.
|
240
|
+
#==Examples
|
241
|
+
# rp.search(/cool/){|pkgname| p pkgname} #=> "cool_levels"
|
242
|
+
# rp.search(/Luiji/i, :authors){|pkgname| p pkgname} #=> All packages created by Luiji or luiji...
|
243
|
+
def search(regexp, *attributes)
|
244
|
+
attributes << :name if attributes.empty? #Default value
|
245
|
+
if attributes == [:name] #Good, no need to download all the specs
|
246
|
+
@packages_list.each{|name| yield(name) if name =~ regexp}
|
247
|
+
else #OK, so we need to download one spec after the other...
|
248
|
+
@packages_list.each do |pkgname|
|
249
|
+
spec = PackageSpecification.from_file(fetch_spec("#{pkgname}.yml", SmcGet.temp_dir))
|
250
|
+
attributes.each do |att|
|
251
|
+
case att
|
252
|
+
when :name then yield(pkgname) if spec.name =~ regexp
|
253
|
+
when :title then yield(pkgname) if spec.title =~ regexp
|
254
|
+
when :authors then yield(pkgname) if spec.authors.any?{|a| a =~ regexp}
|
255
|
+
when :difficulty then yield(pkgname) if spec.difficulty =~ regexp
|
256
|
+
when :description then yield(pkgname) if spec.description =~ regexp
|
257
|
+
when :levels then yield(pkgname) if spec.levels.any?{|l| l =~ regexp}
|
258
|
+
when :music then yield(pkgname) if spec.music.any?{|m| m =~ regexp}
|
259
|
+
when :sounds then yield(pkgname) if spec.sound.any?{|s| s =~ regexp}
|
260
|
+
when :graphics then yield(pkgname) if spec.graphics.any?{|g| g =~ regexp}
|
261
|
+
when :worlds then yield(pkgname) if spec.worlds.any?{|w| w =~ regexp}
|
262
|
+
else
|
263
|
+
$stderr.puts("Warning: Unknown attribute #{att}, ignoring it.")
|
264
|
+
end #case
|
265
|
+
end #attributes.each
|
266
|
+
end # @packages.each
|
267
|
+
end #if only :name
|
268
|
+
end #search
|
269
|
+
|
270
|
+
end #RemoteRepository
|
271
|
+
|
272
|
+
end #SmcGet
|