smc-get 0.2.0.beta1 → 0.3.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.
@@ -1,277 +1,290 @@
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 LocalRepository contains all the packages that are installed locally, i.e.
24
- #that have been downloaded and added to your local SMC installation.
25
- #It provides the exact same methods as RemoteRepository, but works completely
26
- #local and therefore doesn’t need an internet connection as RemoteRepository does.
27
- #
28
- #The only notable difference is that instances of this class have some
29
- #attributes different from those defined for RemoteRepository, but usually
30
- #you shouldn’t have to worry about that.
31
- #
32
- #Concluding, you’ll find the documentation of most of the methods of this class
33
- #in the documentation of the RemoteRepository class, because duplicating docs
34
- #doesn’t make much sense.
35
- class LocalRepository < Repository
36
-
37
- #Directory where the package specs are kept.
38
- SPECS_DIR = Pathname.new("packages")
39
- #Directory where downloaded packages are cached.
40
- CACHE_DIR = Pathname.new("cache")
41
- #Directory where the packages’ level files are kept.
42
- CONTRIB_LEVELS_DIR = Pathname.new("levels") #Levels in subdirectories are currently not recognized by SMC
43
- #Directory where the packages’ music files are kept.
44
- CONTRIB_MUSIC_DIR = Pathname.new("music") + "contrib-music"
45
- #Directory where the packages’ graphic files are kept.
46
- CONTRIB_GRAPHICS_DIR = Pathname.new("pixmaps") + "contrib-graphics"
47
- #Directory where the packages’ sound files are kept.
48
- CONTRIB_SOUNDS_DIR = Pathname.new("sounds") + "contrib-sounds"
49
- #Directory where the packages’ world files are kept
50
- CONTRIB_WORLDS_DIR = Pathname.new("world") #Worlds in subdirectores are currently not recognized by SMC
51
-
52
- #Root path of the local repository. Should be the same as your SMC’s
53
- #installation path.
54
- attr_reader :path
55
- #This repository’s specs dir.
56
- attr_reader :specs_dir
57
- #This repository’s cache dir.
58
- attr_reader :cache_dir
59
- #This repository’s package levels dir.
60
- attr_reader :contrib_level_dir
61
- #This repository’s package music dir.
62
- attr_reader :contrib_music_dir
63
- #This repository’s package graphics dir.
64
- attr_reader :contrib_graphics_dir
65
- #This repository’s package sounds dir.
66
- attr_reader :contrib_sounds_dir
67
- #This repository’s package worlds dir.
68
- attr_reader :contrib_worlds_dir
69
- #An array of PackageSpecification objects containing the specs of
70
- #all packages installed in this repository.
71
- attr_reader :package_specs
72
-
73
- #"Creates" a new local repository whose root is located at the given +path+.
74
- #When instanciating this class, you should point it to the root of your
75
- #SMC installation’s *share* directory, e.g. <b>/usr/share/smc</b>.
76
- #==Parameter
77
- #[path] The path to your SMC installation.
78
- #==Return value
79
- #The newly created LocalRepository.
80
- #==Example
81
- # lr = SmcGet::LocalRepository.new("/usr/share/smc")
82
- #==Remarks
83
- #smc-get requires some additional directories in your SMC installation,
84
- #namely (where +smc+ is your SMC’s *share* directory):
85
- # * smc/packages
86
- # * smc/music/contrib-music
87
- # * smc/sounds/contrib-sounds
88
- # * smc/pixmaps/contrib-graphics
89
- #These will be created when you call this method, so make sure
90
- #you have the appropriate permissions for these directories or
91
- #you’ll get an Errno::EACCES exception when calling ::new.
92
- def initialize(path)
93
- @path = Pathname.new(path)
94
- @specs_dir = @path + SPECS_DIR
95
- @cache_dir = @path + CACHE_DIR
96
- @levels_dir = @path + CONTRIB_LEVELS_DIR
97
- @music_dir = @path + CONTRIB_MUSIC_DIR
98
- @graphics_dir = @path + CONTRIB_GRAPHICS_DIR
99
- @sounds_dir = @path + CONTRIB_SOUNDS_DIR
100
- @worlds_dir = @path + CONTRIB_WORLDS_DIR
101
-
102
- #Create the directories if they’re not there yet
103
- [@specs_dir, @cache_dir, @levels_dir, @music_dir, @graphics_dir, @sounds_dir, @worlds_dir].each do |dir|
104
- dir.mkpath unless dir.directory?
105
- end
106
-
107
- @package_specs = []
108
- @specs_dir.children.each do |spec_path|
109
- next unless spec_path.to_s.end_with?(".yml")
110
- @package_specs << PackageSpecification.from_file(spec_path)
111
- end
112
- end
113
-
114
- def fetch_spec(spec_file, directory = ".")
115
- directory = Pathname.new(directory)
116
-
117
- spec_file_path = @specs_dir + spec_file
118
- raise(Errors::NoSuchResourceError.new(:spec, spec_file), "Package specification '#{spec_file}' not found in the local repository '#{to_s}'!") unless spec_file_path.file?
119
-
120
- directory.mktree unless directory.directory?
121
-
122
- #No need to really "fetch" the spec--this is a *local* repository.
123
- FileUtils.cp(spec_file_path, directory)
124
- directory + spec_file
125
- end
126
-
127
- def fetch_package(pkg_file, directory = ".")
128
- directory = Pathname.new(directory)
129
-
130
- pkg_file_path = @cache_dir + pkg_file
131
- raise(Errors::NoSuchPackageError.new(pkg_file.sub(/\.smcpak/, "")), "Package file '#{pkg_file}' not found in this repository's cache!") unless pkg_file_path.file?
132
-
133
- directory.mktree unless directory.directory?
134
-
135
- #No need to really "fetch" the package--this is a *local* repository
136
- FileUtils.cp(pkg_file_path, directory)
137
- directory + pkg_file
138
- end
139
-
140
- #Installs a package into this local repository in a way that SMC will find
141
- #it’s contents.
142
- #==Parameter
143
- #[package] An instance of class Package. The package to install,
144
- #==Example
145
- # lr.install(a_package)
146
- #==Remarks
147
- #Ensure you have write permissions to the repository, otherwise
148
- #you’ll get an Errno::EACCES exception from this method.
149
- def install(package)
150
- path = package.decompress(SmcGet.temp_dir) + package.spec.name
151
-
152
- package.spec.save(@specs_dir)
153
-
154
- FileUtils.cp_r(path.join(Package::LEVELS_DIR).children, @levels_dir)
155
- FileUtils.cp_r(path.join(Package::MUSIC_DIR).children, @music_dir)
156
- FileUtils.cp_r(path.join(Package::GRAPHICS_DIR).children, @graphics_dir)
157
- FileUtils.cp_r(path.join(Package::SOUNDS_DIR).children, @sounds_dir)
158
- FileUtils.cp_r(path.join(Package::WORLDS_DIR).children, @worlds_dir)
159
-
160
- FileUtils.cp(package.path, @cache_dir)
161
-
162
- @package_specs << package.spec #This package is now installed and therefore the spec must be in that array
163
- end
164
-
165
- #call-seq:
166
- # uninstall(pkg_name)
167
- # uninstall(pkg_name){|path| ...}
168
- #
169
- #Uninstalls a package by removing all files it owns. This method checks the
170
- #checksums specified in the respective package specifications and if it
171
- #detects a user-modified file, the given block is invoked. If the block
172
- #evaluates to a truth value, the modified file is copied to a file
173
- #in the same directory as the original with ".MODIFIED" just before the
174
- #file extension. The blockless form always discards all modified files.
175
- #==Parameters
176
- #[pkg_name] The name of the package (without the .smcpak extension) to remove.
177
- #[full_path] *Blockargument*. The full Pathname of a file that has been modified.
178
- #==Example
179
- # # Delete a package and save all modified files
180
- # rr.uninstall("cool-world"){|file| true}
181
- # # Delete a package and discard all modified files
182
- # rr.uninstall("cool-world")
183
- def uninstall(pkg_name)
184
- spec = @package_specs.find{|spec| spec.name == pkg_name}
185
-
186
- [:levels, :music, :sounds, :graphics].each do |sym|
187
- contrib_dir = @path + self.class.const_get(:"CONTRIB_#{sym.upcase}_DIR")
188
-
189
- #Delete all the files
190
- files = spec[sym]
191
- files.each do |filename|
192
- full_path = contrib_dir + filename
193
- #Check if the file was modified
194
- if block_given? and Digest::SHA1.hexdigest(File.read(full_path)) != spec[:checksums][sym.to_s][filename] #to_s as the keys are strings there, see PackageSpecification.from_file
195
- if yield(full_path) #Getting a truth value from the block means copying
196
- FileUtils.cp(full_path, full_path.parent + filename.sub(/\.(.*?)$/, '.MODIFIED.\1'))
197
- end
198
- end
199
- File.delete(full_path)
200
- end
201
-
202
- #Delete now empty directories
203
- loop do
204
- empty_dirs = []
205
- contrib_dir.find do |path|
206
- next if path == contrib_dir #We surely don’t want to delete the toplevel dir.
207
- empty_dirs << path if path.directory? and path.children.empty?
208
- end
209
- #If no empty directories are present anymore, break out of the loop.
210
- break if empty_dirs.empty?
211
- #Otherwise delete the empty directories and redo the process, because
212
- #the parent directories could be empty now.
213
- empty_dirs.each{|path| File.delete(path)}
214
- end
215
- end
216
-
217
- #Delete worlds as well. Worlds can’t reside in subdirectories, therefore it’s unnecessary
218
- #to check for the empty-directory thing.
219
- spec[:worlds].each do |dirname|
220
- full_path = @worlds_dir + dirname
221
- #Check if any of the world’s files has been modified
222
- ["description.xml", "layer.xml", "world.xml"].each do |wfile|
223
- full_wfile_path = full_path + wfile
224
- if block_given? and Digest::SHA1.hexdigest(full_wfile_path) != spec[:checksums]["worlds"][dirname][wfile] #"worlds" is a string for technical reasons, see PackageSpecification.from_file
225
- if yield(full_wfile_path) #Getting a truth value from the block means copying
226
- FileUtils.cp_r(full_path, full_path.parent + "#{dirname}.MODIFIED")
227
- break #Break from the inner iteration, we just need to copy once
228
- end
229
- end
230
- end
231
- FileUtils.rm_r(full_path)
232
- end
233
-
234
- File.delete(@specs_dir + spec.spec_file_name) #Remove the spec itself
235
- @package_specs.delete(spec) #Otherwise we have a stale package in the array
236
- end
237
-
238
- #Returns the path this repository refers to.
239
- def to_s
240
- @path.to_s
241
- end
242
-
243
- def contain?(pkg)
244
- if pkg.kind_of? Package
245
- @package_specs.include?(pkg.spec)
246
- else
247
- @package_specs.any?{|spec| spec.name == pkg}
248
- end
249
- end
250
- alias contains? contain?
251
-
252
- def search(regexp, *attributes)
253
- attributes << :name if attributes.empty? #Default value
254
-
255
- @package_specs.each do |spec|
256
- attributes.each do |att|
257
- case att
258
- when :name then yield(spec.name) if spec.name =~ regexp
259
- when :title then yield(spec.name) if spec.title =~ regexp
260
- when :authors then yield(spec.name) if spec.authors.any?{|a| a =~ regexp}
261
- when :difficulty then yield(spec.name) if spec.difficulty =~ regexp
262
- when :description then yield(spec.name) if spec.description =~ regexp
263
- when :levels then yield(spec.name) if spec.levels.any?{|l| l =~ regexp}
264
- when :music then yield(spec.name) if spec.music.any?{|m| m =~ regexp}
265
- when :sounds then yield(spec.name) if spec.sound.any?{|s| s =~ regexp}
266
- when :graphics then yield(spec.name) if spec.graphics.any?{|g| g =~ regexp}
267
- when :worlds then yield(spec.name) if spec.worlds.any?{|w| w =~ regexp}
268
- else
269
- $stderr.puts("Warning: Unknown attribute #{att}, ignoring it.")
270
- end #case
271
- end #attributes.each
272
- end # @package_specs.each
273
- end #search
274
-
275
- end #LocalRepository
276
-
277
- end
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 LocalRepository contains all the packages that are installed locally, i.e.
24
+ #that have been downloaded and added to your local SMC installation.
25
+ #It provides the exact same methods as RemoteRepository, but works completely
26
+ #local and therefore doesn’t need an internet connection as RemoteRepository does.
27
+ #
28
+ #The only notable difference is that instances of this class have some
29
+ #attributes different from those defined for RemoteRepository, but usually
30
+ #you shouldn’t have to worry about that.
31
+ #
32
+ #Concluding, you’ll find the documentation of most of the methods of this class
33
+ #in the documentation of the RemoteRepository class, because duplicating docs
34
+ #doesn’t make much sense.
35
+ class LocalRepository < Repository
36
+
37
+ #Directory where the package specs are kept.
38
+ SPECS_DIR = Pathname.new("packages")
39
+ #Directory where downloaded packages are cached.
40
+ CACHE_DIR = Pathname.new("cache")
41
+ #Directory where the packages’ level files are kept.
42
+ CONTRIB_LEVELS_DIR = Pathname.new("levels") #Levels in subdirectories are currently not recognized by SMC
43
+ #Directory where the packages’ music files are kept.
44
+ CONTRIB_MUSIC_DIR = Pathname.new("music") + "contrib-music"
45
+ #Directory where the packages’ graphic files are kept.
46
+ CONTRIB_GRAPHICS_DIR = Pathname.new("pixmaps") + "contrib-graphics"
47
+ #Directory where the packages’ sound files are kept.
48
+ CONTRIB_SOUNDS_DIR = Pathname.new("sounds") + "contrib-sounds"
49
+ #Directory where the packages’ world files are kept
50
+ CONTRIB_WORLDS_DIR = Pathname.new("world") #Worlds in subdirectores are currently not recognized by SMC
51
+
52
+ #Root path of the local repository. Should be the same as your SMC’s
53
+ #installation path.
54
+ attr_reader :path
55
+ #This repository’s specs dir.
56
+ attr_reader :specs_dir
57
+ #This repository’s cache dir.
58
+ attr_reader :cache_dir
59
+ #This repository’s package levels dir.
60
+ attr_reader :contrib_level_dir
61
+ #This repository’s package music dir.
62
+ attr_reader :contrib_music_dir
63
+ #This repository’s package graphics dir.
64
+ attr_reader :contrib_graphics_dir
65
+ #This repository’s package sounds dir.
66
+ attr_reader :contrib_sounds_dir
67
+ #This repository’s package worlds dir.
68
+ attr_reader :contrib_worlds_dir
69
+ #An array of PackageSpecification objects containing the specs of
70
+ #all packages installed in this repository.
71
+ attr_reader :package_specs
72
+
73
+ #"Creates" a new local repository whose root is located at the given +path+.
74
+ #When instanciating this class, you should point it to the root of your
75
+ #SMC installation’s *share* directory, e.g. <b>/usr/share/smc</b>.
76
+ #==Parameter
77
+ #[path] The path to your SMC installation.
78
+ #==Return value
79
+ #The newly created LocalRepository.
80
+ #==Example
81
+ # lr = SmcGet::LocalRepository.new("/usr/share/smc")
82
+ #==Remarks
83
+ #smc-get requires some additional directories in your SMC installation,
84
+ #namely (where +smc+ is your SMC’s *share* directory):
85
+ # * smc/packages
86
+ # * smc/music/contrib-music
87
+ # * smc/sounds/contrib-sounds
88
+ # * smc/pixmaps/contrib-graphics
89
+ #These will be created when you call this method, so make sure
90
+ #you have the appropriate permissions for these directories or
91
+ #you’ll get an Errno::EACCES exception when calling ::new.
92
+ def initialize(path)
93
+ @path = Pathname.new(path)
94
+ @specs_dir = @path + SPECS_DIR
95
+ @cache_dir = @path + CACHE_DIR
96
+ @levels_dir = @path + CONTRIB_LEVELS_DIR
97
+ @music_dir = @path + CONTRIB_MUSIC_DIR
98
+ @graphics_dir = @path + CONTRIB_GRAPHICS_DIR
99
+ @sounds_dir = @path + CONTRIB_SOUNDS_DIR
100
+ @worlds_dir = @path + CONTRIB_WORLDS_DIR
101
+
102
+ #Create the directories if they’re not there yet
103
+ [@specs_dir, @cache_dir, @levels_dir, @music_dir, @graphics_dir, @sounds_dir, @worlds_dir].each do |dir|
104
+ begin
105
+ dir.mkpath unless dir.directory?
106
+ rescue Errno::EACCES #This is fatal, don’t raise InvalidRepository
107
+ $stderr.puts("FATAL: Can't create the directory #{dir}, do you have write permissions there? ")
108
+ exit 2
109
+ end
110
+ end
111
+
112
+ @package_specs = []
113
+ @specs_dir.children.each do |spec_path|
114
+ next unless spec_path.to_s.end_with?(".yml")
115
+ @package_specs << PackageSpecification.from_file(spec_path)
116
+ end
117
+ rescue Errors::InvalidSpecification => e
118
+ raise(Errors::InvalidRepository.new(@path), "Repository contains an invalid specification: #{e.message}")
119
+ rescue => e
120
+ raise(Errors::InvalidRepository.new(@path), e.message)
121
+ end
122
+
123
+ def fetch_spec(spec_file, directory = ".")
124
+ directory = Pathname.new(directory)
125
+
126
+ spec_file_path = @specs_dir + spec_file
127
+ raise(Errors::NoSuchResourceError.new(:spec, spec_file), "Package specification '#{spec_file}' not found in the local repository '#{to_s}'!") unless spec_file_path.file?
128
+
129
+ directory.mktree unless directory.directory?
130
+
131
+ #No need to really "fetch" the spec--this is a *local* repository.
132
+ FileUtils.cp(spec_file_path, directory)
133
+ directory + spec_file
134
+ end
135
+
136
+ def fetch_package(pkg_file, directory = ".")
137
+ directory = Pathname.new(directory)
138
+
139
+ pkg_file_path = @cache_dir + pkg_file
140
+ raise(Errors::NoSuchPackageError.new(pkg_file.sub(/\.smcpak/, "")), "Package file '#{pkg_file}' not found in this repository's cache!") unless pkg_file_path.file?
141
+
142
+ directory.mktree unless directory.directory?
143
+
144
+ #No need to really "fetch" the package--this is a *local* repository
145
+ FileUtils.cp(pkg_file_path, directory)
146
+ directory + pkg_file
147
+ end
148
+
149
+ #Installs a package into this local repository in a way that SMC will find
150
+ #it’s contents.
151
+ #==Parameter
152
+ #[package] An instance of class Package. The package to install,
153
+ #==Example
154
+ # lr.install(a_package)
155
+ #==Remarks
156
+ #Ensure you have write permissions to the repository, otherwise
157
+ #you’ll get an Errno::EACCES exception from this method.
158
+ def install(package)
159
+ path = package.decompress(SmcGet.temp_dir) + package.spec.name
160
+
161
+ unless package.spec.valid?
162
+ raise(Errors::InvalidSpecification, package.spec.validate.first)
163
+ end
164
+
165
+ package.spec.save(@specs_dir)
166
+
167
+ FileUtils.cp_r(path.join(Package::LEVELS_DIR).children, @levels_dir)
168
+ FileUtils.cp_r(path.join(Package::MUSIC_DIR).children, @music_dir)
169
+ FileUtils.cp_r(path.join(Package::GRAPHICS_DIR).children, @graphics_dir)
170
+ FileUtils.cp_r(path.join(Package::SOUNDS_DIR).children, @sounds_dir)
171
+ FileUtils.cp_r(path.join(Package::WORLDS_DIR).children, @worlds_dir)
172
+
173
+ FileUtils.cp(package.path, @cache_dir)
174
+
175
+ @package_specs << package.spec #This package is now installed and therefore the spec must be in that array
176
+ end
177
+
178
+ #call-seq:
179
+ # uninstall(pkg_name)
180
+ # uninstall(pkg_name){|path| ...}
181
+ #
182
+ #Uninstalls a package by removing all files it owns. This method checks the
183
+ #checksums specified in the respective package specifications and if it
184
+ #detects a user-modified file, the given block is invoked. If the block
185
+ #evaluates to a truth value, the modified file is copied to a file
186
+ #in the same directory as the original with ".MODIFIED" just before the
187
+ #file extension. The blockless form always discards all modified files.
188
+ #==Parameters
189
+ #[pkg_name] The name of the package (without the .smcpak extension) to remove.
190
+ #[full_path] *Blockargument*. The full Pathname of a file that has been modified.
191
+ #==Example
192
+ # # Delete a package and save all modified files
193
+ # rr.uninstall("cool-world"){|file| true}
194
+ # # Delete a package and discard all modified files
195
+ # rr.uninstall("cool-world")
196
+ def uninstall(pkg_name)
197
+ spec = @package_specs.find{|spec| spec.name == pkg_name}
198
+
199
+ [:levels, :music, :sounds, :graphics].each do |sym|
200
+ contrib_dir = @path + self.class.const_get(:"CONTRIB_#{sym.upcase}_DIR")
201
+
202
+ #Delete all the files
203
+ files = spec[sym]
204
+ files.each do |filename|
205
+ full_path = contrib_dir + filename
206
+ #Check if the file was modified
207
+ if block_given? and Digest::SHA1.hexdigest(File.read(full_path)) != spec[:checksums][sym.to_s][filename] #to_s as the keys are strings there, see PackageSpecification.from_file
208
+ if yield(full_path) #Getting a truth value from the block means copying
209
+ FileUtils.cp(full_path, full_path.parent + filename.sub(/\.(.*?)$/, '.MODIFIED.\1'))
210
+ end
211
+ end
212
+ File.delete(full_path)
213
+ end
214
+
215
+ #Delete now empty directories
216
+ loop do
217
+ empty_dirs = []
218
+ contrib_dir.find do |path|
219
+ next if path == contrib_dir #We surely don’t want to delete the toplevel dir.
220
+ empty_dirs << path if path.directory? and path.children.empty?
221
+ end
222
+ #If no empty directories are present anymore, break out of the loop.
223
+ break if empty_dirs.empty?
224
+ #Otherwise delete the empty directories and redo the process, because
225
+ #the parent directories could be empty now.
226
+ empty_dirs.each{|path| FileUtils.rmdir(path)}
227
+ end
228
+ end
229
+
230
+ #Delete worlds as well. Worlds can’t reside in subdirectories, therefore it’s unnecessary
231
+ #to check for the empty-directory thing.
232
+ spec[:worlds].each do |dirname|
233
+ full_path = @worlds_dir + dirname
234
+ #Check if any of the world’s files has been modified
235
+ ["description.xml", "layer.xml", "world.xml"].each do |wfile|
236
+ full_wfile_path = full_path + wfile
237
+ if block_given? and Digest::SHA1.hexdigest(File.read(full_wfile_path)) != spec[:checksums]["worlds"][dirname][wfile] #"worlds" is a string for technical reasons, see PackageSpecification.from_file
238
+ if yield(full_wfile_path) #Getting a truth value from the block means copying
239
+ FileUtils.cp_r(full_path, full_path.parent + "#{dirname}.MODIFIED")
240
+ break #Break from the inner iteration, we just need to copy once
241
+ end
242
+ end
243
+ end
244
+ FileUtils.rm_r(full_path)
245
+ end
246
+
247
+ File.delete(@specs_dir + spec.spec_file_name) #Remove the spec itself
248
+ @package_specs.delete(spec) #Otherwise we have a stale package in the array
249
+ end
250
+
251
+ #Returns the path this repository refers to.
252
+ def to_s
253
+ @path.to_s
254
+ end
255
+
256
+ def contain?(pkg)
257
+ if pkg.kind_of? Package
258
+ @package_specs.include?(pkg.spec)
259
+ else
260
+ @package_specs.any?{|spec| spec.name == pkg}
261
+ end
262
+ end
263
+ alias contains? contain?
264
+
265
+ def search(regexp, *attributes)
266
+ attributes << :name if attributes.empty? #Default value
267
+
268
+ @package_specs.each do |spec|
269
+ attributes.each do |att|
270
+ case att
271
+ when :name then yield(spec.name) if spec.name =~ regexp
272
+ when :title then yield(spec.name) if spec.title =~ regexp
273
+ when :authors then yield(spec.name) if spec.authors.any?{|a| a =~ regexp}
274
+ when :difficulty then yield(spec.name) if spec.difficulty =~ regexp
275
+ when :description then yield(spec.name) if spec.description =~ regexp
276
+ when :levels then yield(spec.name) if spec.levels.any?{|l| l =~ regexp}
277
+ when :music then yield(spec.name) if spec.music.any?{|m| m =~ regexp}
278
+ when :sounds then yield(spec.name) if spec.sound.any?{|s| s =~ regexp}
279
+ when :graphics then yield(spec.name) if spec.graphics.any?{|g| g =~ regexp}
280
+ when :worlds then yield(spec.name) if spec.worlds.any?{|w| w =~ regexp}
281
+ else
282
+ $stderr.puts("Warning: Unknown attribute #{att}, ignoring it.")
283
+ end #case
284
+ end #attributes.each
285
+ end # @package_specs.each
286
+ end #search
287
+
288
+ end #LocalRepository
289
+
290
+ end