mezza-rubyzip 0.9.4.1

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.
@@ -0,0 +1,66 @@
1
+ module Zip
2
+ class ZipEntrySet #:nodoc:all
3
+ include Enumerable
4
+
5
+ def initialize(anEnumerable = [])
6
+ super()
7
+ @entrySet = {}
8
+ anEnumerable.each { |o| push(o) }
9
+ end
10
+
11
+ def include?(entry)
12
+ @entrySet.include?(entry.to_s)
13
+ end
14
+
15
+ def <<(entry)
16
+ @entrySet[entry.to_s] = entry
17
+ end
18
+ alias :push :<<
19
+
20
+ def size
21
+ @entrySet.size
22
+ end
23
+ alias :length :size
24
+
25
+ def delete(entry)
26
+ @entrySet.delete(entry.to_s) ? entry : nil
27
+ end
28
+
29
+ def each(&aProc)
30
+ @entrySet.values.each(&aProc)
31
+ end
32
+
33
+ def entries
34
+ @entrySet.values
35
+ end
36
+
37
+ # deep clone
38
+ def dup
39
+ newZipEntrySet = ZipEntrySet.new(@entrySet.values.map { |e| e.dup })
40
+ end
41
+
42
+ def == (other)
43
+ return false unless other.kind_of?(ZipEntrySet)
44
+ return @entrySet == other.entrySet
45
+ end
46
+
47
+ def parent(entry)
48
+ @entrySet[entry.parent_as_string]
49
+ end
50
+
51
+ def glob(pattern, flags = ::File::FNM_PATHNAME|::File::FNM_DOTMATCH)
52
+ entries.select {
53
+ |entry|
54
+ ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
55
+ }
56
+ end
57
+
58
+ #TODO attr_accessor :auto_create_directories
59
+ protected
60
+ attr_accessor :entrySet
61
+ end
62
+ end
63
+
64
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
65
+ # rubyzip is free software; you can redistribute it and/or
66
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,213 @@
1
+ module Zip
2
+ class ZipExtraField < Hash
3
+ ID_MAP = {}
4
+
5
+ # Meta class for extra fields
6
+ class Generic
7
+ def self.register_map
8
+ if self.const_defined?(:HEADER_ID)
9
+ ID_MAP[self.const_get(:HEADER_ID)] = self
10
+ end
11
+ end
12
+
13
+ def self.name
14
+ self.to_s.split("::")[-1]
15
+ end
16
+
17
+ # return field [size, content] or false
18
+ def initial_parse(binstr)
19
+ if ! binstr
20
+ # If nil, start with empty.
21
+ return false
22
+ elsif binstr[0,2] != self.class.const_get(:HEADER_ID)
23
+ $stderr.puts "Warning: weired extra feild header ID. skip parsing"
24
+ return false
25
+ end
26
+ [binstr[2,2].unpack("v")[0], binstr[4..-1]]
27
+ end
28
+
29
+ def ==(other)
30
+ self.class != other.class and return false
31
+ each { |k, v|
32
+ v != other[k] and return false
33
+ }
34
+ true
35
+ end
36
+
37
+ def to_local_bin
38
+ s = pack_for_local
39
+ self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
40
+ end
41
+
42
+ def to_c_dir_bin
43
+ s = pack_for_c_dir
44
+ self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s
45
+ end
46
+ end
47
+
48
+ # Info-ZIP Additional timestamp field
49
+ class UniversalTime < Generic
50
+ HEADER_ID = "UT"
51
+ register_map
52
+
53
+ def initialize(binstr = nil)
54
+ @ctime = nil
55
+ @mtime = nil
56
+ @atime = nil
57
+ @flag = nil
58
+ binstr and merge(binstr)
59
+ end
60
+ attr_accessor :atime, :ctime, :mtime, :flag
61
+
62
+ def merge(binstr)
63
+ binstr == "" and return
64
+ size, content = initial_parse(binstr)
65
+ size or return
66
+ @flag, mtime, atime, ctime = content.unpack("CVVV")
67
+ mtime and @mtime ||= Time.at(mtime)
68
+ atime and @atime ||= Time.at(atime)
69
+ ctime and @ctime ||= Time.at(ctime)
70
+ end
71
+
72
+ def ==(other)
73
+ @mtime == other.mtime &&
74
+ @atime == other.atime &&
75
+ @ctime == other.ctime
76
+ end
77
+
78
+ def pack_for_local
79
+ s = [@flag].pack("C")
80
+ @flag & 1 != 0 and s << [@mtime.to_i].pack("V")
81
+ @flag & 2 != 0 and s << [@atime.to_i].pack("V")
82
+ @flag & 4 != 0 and s << [@ctime.to_i].pack("V")
83
+ s
84
+ end
85
+
86
+ def pack_for_c_dir
87
+ s = [@flag].pack("C")
88
+ @flag & 1 == 1 and s << [@mtime.to_i].pack("V")
89
+ s
90
+ end
91
+ end
92
+
93
+ # Info-ZIP Extra for UNIX uid/gid
94
+ class IUnix < Generic
95
+ HEADER_ID = "Ux"
96
+ register_map
97
+
98
+ def initialize(binstr = nil)
99
+ @uid = 0
100
+ @gid = 0
101
+ binstr and merge(binstr)
102
+ end
103
+ attr_accessor :uid, :gid
104
+
105
+ def merge(binstr)
106
+ binstr == "" and return
107
+ size, content = initial_parse(binstr)
108
+ # size: 0 for central direcotry. 4 for local header
109
+ return if(! size || size == 0)
110
+ uid, gid = content.unpack("vv")
111
+ @uid ||= uid
112
+ @gid ||= gid
113
+ end
114
+
115
+ def ==(other)
116
+ @uid == other.uid &&
117
+ @gid == other.gid
118
+ end
119
+
120
+ def pack_for_local
121
+ [@uid, @gid].pack("vv")
122
+ end
123
+
124
+ def pack_for_c_dir
125
+ ""
126
+ end
127
+ end
128
+
129
+ ## start main of ZipExtraField < Hash
130
+ def initialize(binstr = nil)
131
+ binstr and merge(binstr)
132
+ end
133
+
134
+ def merge(binstr)
135
+ binstr == "" and return
136
+ i = 0
137
+ while i < binstr.length
138
+ id = binstr[i,2]
139
+ len = binstr[i+2,2].to_s.unpack("v")[0]
140
+ if id && ID_MAP.member?(id)
141
+ field_name = ID_MAP[id].name
142
+ if self.member?(field_name)
143
+ self[field_name].mergea(binstr[i, len+4])
144
+ else
145
+ field_obj = ID_MAP[id].new(binstr[i, len+4])
146
+ self[field_name] = field_obj
147
+ end
148
+ elsif id
149
+ unless self["Unknown"]
150
+ s = ""
151
+ class << s
152
+ alias_method :to_c_dir_bin, :to_s
153
+ alias_method :to_local_bin, :to_s
154
+ end
155
+ self["Unknown"] = s
156
+ end
157
+ if ! len || len+4 > binstr[i..-1].length
158
+ self["Unknown"] << binstr[i..-1]
159
+ break;
160
+ end
161
+ self["Unknown"] << binstr[i, len+4]
162
+ end
163
+ i += len+4
164
+ end
165
+ end
166
+
167
+ def create(name)
168
+ field_class = nil
169
+ ID_MAP.each { |id, klass|
170
+ if klass.name == name
171
+ field_class = klass
172
+ break
173
+ end
174
+ }
175
+ if ! field_class
176
+ raise ZipError, "Unknown extra field '#{name}'"
177
+ end
178
+ self[name] = field_class.new()
179
+ end
180
+
181
+ def to_local_bin
182
+ s = ""
183
+ each { |k, v|
184
+ s << v.to_local_bin
185
+ }
186
+ s
187
+ end
188
+ alias :to_s :to_local_bin
189
+
190
+ def to_c_dir_bin
191
+ s = ""
192
+ each { |k, v|
193
+ s << v.to_c_dir_bin
194
+ }
195
+ s
196
+ end
197
+
198
+ def c_dir_length
199
+ to_c_dir_bin.length
200
+ end
201
+ def local_length
202
+ to_local_bin.length
203
+ end
204
+ alias :c_dir_size :c_dir_length
205
+ alias :local_size :local_length
206
+ alias :length :local_length
207
+ alias :size :local_length
208
+ end
209
+ end
210
+
211
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
212
+ # rubyzip is free software; you can redistribute it and/or
213
+ # modify it under the terms of the ruby license.
@@ -0,0 +1,310 @@
1
+ module Zip
2
+ # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK.
3
+ # The most important methods are those inherited from
4
+ # ZipCentralDirectory for accessing information about the entries in
5
+ # the archive and methods such as get_input_stream and
6
+ # get_output_stream for reading from and writing entries to the
7
+ # archive. The class includes a few convenience methods such as
8
+ # #extract for extracting entries to the filesystem, and #remove,
9
+ # #replace, #rename and #mkdir for making simple modifications to
10
+ # the archive.
11
+ #
12
+ # Modifications to a zip archive are not committed until #commit or
13
+ # #close is called. The method #open accepts a block following
14
+ # the pattern from File.open offering a simple way to
15
+ # automatically close the archive when the block returns.
16
+ #
17
+ # The following example opens zip archive <code>my.zip</code>
18
+ # (creating it if it doesn't exist) and adds an entry
19
+ # <code>first.txt</code> and a directory entry <code>a_dir</code>
20
+ # to it.
21
+ #
22
+ # require 'zip/zip'
23
+ #
24
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
25
+ # |zipfile|
26
+ # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" }
27
+ # zipfile.mkdir("a_dir")
28
+ # }
29
+ #
30
+ # The next example reopens <code>my.zip</code> writes the contents of
31
+ # <code>first.txt</code> to standard out and deletes the entry from
32
+ # the archive.
33
+ #
34
+ # require 'zip/zip'
35
+ #
36
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
37
+ # |zipfile|
38
+ # puts zipfile.read("first.txt")
39
+ # zipfile.remove("first.txt")
40
+ # }
41
+ #
42
+ # ZipFileSystem offers an alternative API that emulates ruby's
43
+ # interface for accessing the filesystem, ie. the File and Dir classes.
44
+
45
+ class ZipFile < ZipCentralDirectory
46
+
47
+ CREATE = 1
48
+
49
+ attr_reader :name
50
+
51
+ # default -> false
52
+ attr_accessor :restore_ownership
53
+ # default -> false
54
+ attr_accessor :restore_permissions
55
+ # default -> true
56
+ attr_accessor :restore_times
57
+
58
+ # Opens a zip archive. Pass true as the second parameter to create
59
+ # a new archive if it doesn't exist already.
60
+ def initialize(fileName, create = nil, buffer = false)
61
+ super()
62
+ @name = fileName
63
+ @comment = ""
64
+ if (::File.exists?(fileName)) and !buffer
65
+ ::File.open(name, "rb") { |f| read_from_stream(f) }
66
+ elsif (create)
67
+ @entrySet = ZipEntrySet.new
68
+ else
69
+ raise ZipError, "File #{fileName} not found"
70
+ end
71
+ @create = create
72
+ @storedEntries = @entrySet.dup
73
+
74
+ @restore_ownership = false
75
+ @restore_permissions = false
76
+ @restore_times = true
77
+ end
78
+
79
+ # Same as #new. If a block is passed the ZipFile object is passed
80
+ # to the block and is automatically closed afterwards just as with
81
+ # ruby's builtin File.open method.
82
+ def ZipFile.open(fileName, create = nil)
83
+ zf = ZipFile.new(fileName, create)
84
+ if block_given?
85
+ begin
86
+ yield zf
87
+ ensure
88
+ zf.close
89
+ end
90
+ else
91
+ zf
92
+ end
93
+ end
94
+
95
+ # Same as #open. But outputs data to a buffer instead of a file
96
+ def ZipFile.add_buffer
97
+ zf = ZipFile.new('', true, true)
98
+ begin
99
+ yield zf
100
+ ensure
101
+ buffer = zf.write_buffer
102
+ return buffer
103
+ end
104
+ end
105
+
106
+ # Returns the zip files comment, if it has one
107
+ attr_accessor :comment
108
+
109
+ # Iterates over the contents of the ZipFile. This is more efficient
110
+ # than using a ZipInputStream since this methods simply iterates
111
+ # through the entries in the central directory structure in the archive
112
+ # whereas ZipInputStream jumps through the entire archive accessing the
113
+ # local entry headers (which contain the same information as the
114
+ # central directory).
115
+ def ZipFile.foreach(aZipFileName, &block)
116
+ ZipFile.open(aZipFileName) {
117
+ |zipFile|
118
+ zipFile.each(&block)
119
+ }
120
+ end
121
+
122
+ # Returns an input stream to the specified entry. If a block is passed
123
+ # the stream object is passed to the block and the stream is automatically
124
+ # closed afterwards just as with ruby's builtin File.open method.
125
+ def get_input_stream(entry, &aProc)
126
+ get_entry(entry).get_input_stream(&aProc)
127
+ end
128
+
129
+ # Returns an output stream to the specified entry. If a block is passed
130
+ # the stream object is passed to the block and the stream is automatically
131
+ # closed afterwards just as with ruby's builtin File.open method.
132
+ def get_output_stream(entry, &aProc)
133
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
134
+ if newEntry.directory?
135
+ raise ArgumentError,
136
+ "cannot open stream to directory entry - '#{newEntry}'"
137
+ end
138
+ zipStreamableEntry = ZipStreamableStream.new(newEntry)
139
+ @entrySet << zipStreamableEntry
140
+ zipStreamableEntry.get_output_stream(&aProc)
141
+ end
142
+
143
+ # Returns the name of the zip archive
144
+ def to_s
145
+ @name
146
+ end
147
+
148
+ # Returns a string containing the contents of the specified entry
149
+ def read(entry)
150
+ get_input_stream(entry) { |is| is.read }
151
+ end
152
+
153
+ # Convenience method for adding the contents of a file to the archive
154
+ def add(entry, srcPath, &continueOnExistsProc)
155
+ continueOnExistsProc ||= proc { false }
156
+ check_entry_exists(entry, continueOnExistsProc, "add")
157
+ newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
158
+ newEntry.gather_fileinfo_from_srcpath(srcPath)
159
+ @entrySet << newEntry
160
+ end
161
+
162
+ # Removes the specified entry.
163
+ def remove(entry)
164
+ @entrySet.delete(get_entry(entry))
165
+ end
166
+
167
+ # Renames the specified entry.
168
+ def rename(entry, newName, &continueOnExistsProc)
169
+ foundEntry = get_entry(entry)
170
+ check_entry_exists(newName, continueOnExistsProc, "rename")
171
+ @entrySet.delete(foundEntry)
172
+ foundEntry.name = newName
173
+ @entrySet << foundEntry
174
+ end
175
+
176
+ # Replaces the specified entry with the contents of srcPath (from
177
+ # the file system).
178
+ def replace(entry, srcPath)
179
+ check_file(srcPath)
180
+ add(remove(entry), srcPath)
181
+ end
182
+
183
+ # Extracts entry to file destPath.
184
+ def extract(entry, destPath, &onExistsProc)
185
+ onExistsProc ||= proc { false }
186
+ foundEntry = get_entry(entry)
187
+ foundEntry.extract(destPath, &onExistsProc)
188
+ end
189
+
190
+ # Commits changes that has been made since the previous commit to
191
+ # the zip archive.
192
+ def commit
193
+ return if ! commit_required?
194
+ on_success_replace(name) {
195
+ |tmpFile|
196
+ ZipOutputStream.open(tmpFile) {
197
+ |zos|
198
+
199
+ @entrySet.each { |e| e.write_to_zip_output_stream(zos) }
200
+ zos.comment = comment
201
+ }
202
+ true
203
+ }
204
+ initialize(name)
205
+ end
206
+
207
+ # Write buffer write changes to buffer and return
208
+ def write_buffer
209
+ buffer = ZipOutputStream.write_buffer do |zos|
210
+ @entrySet.each { |e| e.write_to_zip_output_stream(zos) }
211
+ zos.comment = comment
212
+ end
213
+ return buffer
214
+ end
215
+
216
+ # Closes the zip file committing any changes that has been made.
217
+ def close
218
+ commit
219
+ end
220
+
221
+ # Returns true if any changes has been made to this archive since
222
+ # the previous commit
223
+ def commit_required?
224
+ return @entrySet != @storedEntries || @create == ZipFile::CREATE
225
+ end
226
+
227
+ # Searches for entry with the specified name. Returns nil if
228
+ # no entry is found. See also get_entry
229
+ def find_entry(entry)
230
+ @entrySet.detect {
231
+ |e|
232
+ e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "")
233
+ }
234
+ end
235
+
236
+ # Searches for an entry just as find_entry, but throws Errno::ENOENT
237
+ # if no entry is found.
238
+ def get_entry(entry)
239
+ selectedEntry = find_entry(entry)
240
+ unless selectedEntry
241
+ raise Errno::ENOENT, entry
242
+ end
243
+ selectedEntry.restore_ownership = @restore_ownership
244
+ selectedEntry.restore_permissions = @restore_permissions
245
+ selectedEntry.restore_times = @restore_times
246
+
247
+ return selectedEntry
248
+ end
249
+
250
+ # Creates a directory
251
+ def mkdir(entryName, permissionInt = 0755)
252
+ if find_entry(entryName)
253
+ raise Errno::EEXIST, "File exists - #{entryName}"
254
+ end
255
+ @entrySet << ZipStreamableDirectory.new(@name, entryName.to_s.ensure_end("/"), nil, permissionInt)
256
+ end
257
+
258
+ private
259
+
260
+ def is_directory(newEntry, srcPath)
261
+ srcPathIsDirectory = ::File.directory?(srcPath)
262
+ if newEntry.is_directory && ! srcPathIsDirectory
263
+ raise ArgumentError,
264
+ "entry name '#{newEntry}' indicates directory entry, but "+
265
+ "'#{srcPath}' is not a directory"
266
+ elsif ! newEntry.is_directory && srcPathIsDirectory
267
+ newEntry.name += "/"
268
+ end
269
+ return newEntry.is_directory && srcPathIsDirectory
270
+ end
271
+
272
+ def check_entry_exists(entryName, continueOnExistsProc, procedureName)
273
+ continueOnExistsProc ||= proc { false }
274
+ if @entrySet.detect { |e| e.name == entryName }
275
+ if continueOnExistsProc.call
276
+ remove get_entry(entryName)
277
+ else
278
+ raise ZipEntryExistsError,
279
+ procedureName+" failed. Entry #{entryName} already exists"
280
+ end
281
+ end
282
+ end
283
+
284
+ def check_file(path)
285
+ unless ::File.readable? path
286
+ raise Errno::ENOENT, path
287
+ end
288
+ end
289
+
290
+ def on_success_replace(aFilename)
291
+ tmpfile = get_tempfile
292
+ tmpFilename = tmpfile.path
293
+ tmpfile.close
294
+ if yield tmpFilename
295
+ ::File.rename(tmpFilename, name)
296
+ end
297
+ end
298
+
299
+ def get_tempfile
300
+ tempFile = Tempfile.new(::File.basename(name), ::File.dirname(name))
301
+ tempFile.binmode
302
+ tempFile
303
+ end
304
+
305
+ end
306
+ end
307
+
308
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
309
+ # rubyzip is free software; you can redistribute it and/or
310
+ # modify it under the terms of the ruby license.