smc-get 0.1.0 → 0.2.0.beta1
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/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
|