bigtinker 0.97 → 0.98

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.
Files changed (184) hide show
  1. data/code/01rwdcore/01rwdcore.rb +6 -6
  2. data/code/01rwdcore/02helptexthashbegin.rb +13 -11
  3. data/code/01rwdcore/jumplinkcommand.rb +14 -4
  4. data/code/01rwdcore/openhelpwindow.rb +7 -0
  5. data/code/01rwdcore/runhelpabout.rb +6 -1
  6. data/code/01rwdcore/runtab.rb +15 -0
  7. data/code/01rwdcore/selectiontab.rb +2 -0
  8. data/code/01rwdcore/setuphelpaboutoptions.rb +2 -0
  9. data/code/01rwdcore/setuptinkerdocuments.rb +1 -0
  10. data/code/01rwdcore/test_cases.rb +100 -51
  11. data/code/01rwdcore/test_harness.rb +8 -1
  12. data/code/01rwdcore/uploadreturns.rb +3 -0
  13. data/code/dd0viewphoto/dd0viewphoto.rb +2 -0
  14. data/code/superant.com.rwdcalendar/gh9calendar.rb +40 -0
  15. data/code/superant.com.rwdcalendar/helptexthashrwdschedule.rb +14 -0
  16. data/code/superant.com.rwdcalendar/openhelpwindowrwdschedule.rb +17 -0
  17. data/code/superant.com.rwdtinkerbackwindow/changelocale.rb +84 -0
  18. data/code/superant.com.rwdtinkerbackwindow/initiateapplets.rb +0 -1
  19. data/code/superant.com.rwdtinkerbackwindow/installgemapplet.rb +2 -0
  20. data/code/superant.com.rwdtinkerbackwindow/listgemzips.rb +2 -0
  21. data/code/superant.com.rwdtinkerbackwindow/listinstalledfiles.rb +3 -1
  22. data/code/superant.com.rwdtinkerbackwindow/listzips.rb +4 -0
  23. data/code/superant.com.rwdtinkerbackwindow/loadconfigurationrecord.rb +3 -1
  24. data/code/superant.com.rwdtinkerbackwindow/openhelpwindowtinkerwin2.rb +3 -1
  25. data/code/superant.com.rwdtinkerbackwindow/showlocaleoptions.rb +9 -0
  26. data/code/superant.com.rwdtinkerbackwindow/viewappletcontents.rb +1 -0
  27. data/code/superant.com.rwdtinkerbackwindow/viewlogfile.rb +8 -5
  28. data/code/superant.com.schedule/0uninstallapplet.rb +24 -0
  29. data/code/superant.com.schedule/archiveevent.rb +14 -0
  30. data/code/superant.com.schedule/archiveicsevent.rb +14 -0
  31. data/code/superant.com.schedule/cleareventscreendisplay.rb +19 -0
  32. data/code/superant.com.schedule/deleteeventrecord.rb +19 -0
  33. data/code/superant.com.schedule/deleteicseventrecord.rb +19 -0
  34. data/code/superant.com.schedule/deleterwdscheduleupdatefiles.rb +20 -0
  35. data/code/superant.com.schedule/downloadrwdschedulefiles.rb +37 -0
  36. data/code/superant.com.schedule/exporticseventrecord.rb +97 -0
  37. data/code/{superant.com.foldeditor → superant.com.schedule}/helptexthashload.rb +2 -2
  38. data/code/superant.com.schedule/listeventdates.rb +19 -0
  39. data/code/superant.com.schedule/listicseventdates.rb +19 -0
  40. data/code/{superant.com.foldeditor → superant.com.schedule}/loadconfigurationrecord.rb +4 -4
  41. data/code/superant.com.schedule/loadconfigurationvariables.rb +14 -0
  42. data/code/superant.com.schedule/loadeventrecord.rb +38 -0
  43. data/code/superant.com.schedule/loadicseventrecord.rb +30 -0
  44. data/code/superant.com.schedule/openhelpwindowrwdschedule.rb +43 -0
  45. data/code/superant.com.schedule/renameeventdata.rb +14 -0
  46. data/code/superant.com.schedule/renameicseventdata.rb +17 -0
  47. data/code/{superant.com.foldeditor/runrwdapplet.rb → superant.com.schedule/returntomain.rb} +3 -3
  48. data/code/superant.com.schedule/runrwdscheduleicsbackwindow.rb +10 -0
  49. data/code/superant.com.schedule/runrwdschedulemenu1.rb +34 -0
  50. data/code/superant.com.schedule/runrwdschedulesyncbackwindow.rb +10 -0
  51. data/code/{superant.com.foldeditor/rwdtinkerversion.rb → superant.com.schedule/rwdversion.rb} +2 -2
  52. data/code/{superant.com.foldeditor → superant.com.schedule}/saveconfigurationrecord.rb +5 -6
  53. data/code/superant.com.schedule/saveeventrecord.rb +25 -0
  54. data/code/superant.com.schedule/saveicseventrecord.rb +98 -0
  55. data/code/superant.com.schedule/syncrwdschedule.rb +30 -0
  56. data/code/superant.com.schedule/test_cases.rb +45 -0
  57. data/code/superant.com.schedule/uploadrwdschedulefiles.rb +30 -0
  58. data/code/superant.com.schedule/viewevent.rb +20 -0
  59. data/code/superant.com.schedule/viewicsevent.rb +20 -0
  60. data/code/superant.com.schedule/viewrwdschedulesconfiguration.rb +21 -0
  61. data/code/zz0applicationend/zz0end.rb +2 -1
  62. data/configuration/bigtinker.dist +5 -2
  63. data/configuration/rwdtinker.dist +3 -3
  64. data/configuration/rwdwschedule.dist +28 -0
  65. data/configuration/tinkerwin2variables.dist +1 -1
  66. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/1appname.rwd +5 -0
  67. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/20downloadftp.rwd +45 -0
  68. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/67viewconfiguration.rwd +29 -0
  69. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/70rwddiagnostics.rwd +16 -0
  70. data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/m01menubegin.rwd +18 -0
  71. data/gui/tinkerbackwindows/{superant.com.foldeditor/9end.rwd → superant.com.rwdschedulebackwindow/zvbackend.rwd} +6 -6
  72. data/gui/tinkerbackwindows/superant.com.rwdschedules/1appname.rwd +5 -0
  73. data/gui/tinkerbackwindows/superant.com.rwdschedules/gg0viewevent.rwd +27 -0
  74. data/gui/tinkerbackwindows/superant.com.rwdschedules/gl6editrecord.rwd +56 -0
  75. data/gui/tinkerbackwindows/superant.com.rwdschedules/gl8contactutilities.rwd +25 -0
  76. data/gui/tinkerbackwindows/superant.com.rwdschedules/hl9calendar.rwd +27 -0
  77. data/gui/tinkerbackwindows/superant.com.rwdschedules/m01menubegin.rwd +18 -0
  78. data/gui/tinkerbackwindows/{superant.com.slideshow/9end.rwd → superant.com.rwdschedules/zvbackend.rwd} +6 -6
  79. data/gui/tinkerbackwindows/superant.com.rwdschedulesback/1appname.rwd +5 -0
  80. data/gui/tinkerbackwindows/superant.com.rwdschedulesback/30viewevent.rwd +27 -0
  81. data/gui/tinkerbackwindows/superant.com.rwdschedulesback/40editrecord.rwd +49 -0
  82. data/gui/tinkerbackwindows/superant.com.rwdschedulesback/60eventicsutilities.rwd +25 -0
  83. data/gui/tinkerbackwindows/superant.com.rwdschedulesback/m01menubegin.rwd +18 -0
  84. data/gui/tinkerbackwindows/superant.com.rwdschedulesback/zvbackend.rwd +6 -0
  85. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/70rwddiagnostics.rwd +1 -1
  86. data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/80localechanger.rwd +17 -0
  87. data/init.rb +7 -1
  88. data/installed/rwdscheduledate2.inf +6 -0
  89. data/installed/rwdwschedule.inf +20 -0
  90. data/lang/en/rwdcalendar/en.po +32 -0
  91. data/lang/en/rwdcore/en.po +31 -0
  92. data/lang/es/rwdcalendar/es.po +10 -0
  93. data/lang/es/rwdcore/es.po +27 -15
  94. data/lang/fr/rwdcalendar/fr.po +11 -0
  95. data/lang/fr/rwdcore/fr.po +4 -1
  96. data/lang/hi/rwdcalendar/hi.po +11 -0
  97. data/lang/hi/rwdcore/hi.po +39 -36
  98. data/lang/ja/rwdcalendar/ja.po +11 -0
  99. data/lang/ja/rwdcore/ja.po +3 -0
  100. data/lang/nl/rwdcalendar/nl.po +12 -0
  101. data/lang/nl/rwdcore/nl.po +4 -1
  102. data/lib/cal.rb +158 -0
  103. data/lib/icalendar.rb +18 -0
  104. data/lib/icalendar/base.rb +17 -0
  105. data/lib/icalendar/calendar.rb +44 -0
  106. data/lib/icalendar/calendar_parser.rb +237 -0
  107. data/lib/icalendar/component.rb +91 -0
  108. data/lib/icalendar/component/alarm.rb +16 -0
  109. data/lib/icalendar/component/event.rb +25 -0
  110. data/lib/icalendar/component/freebusy.rb +12 -0
  111. data/lib/icalendar/component/journal.rb +25 -0
  112. data/lib/icalendar/component/timezone.rb +26 -0
  113. data/lib/icalendar/component/todo.rb +21 -0
  114. data/lib/icalendar/helpers.rb +103 -0
  115. data/lib/icalendar/parameter.rb +25 -0
  116. data/lib/zip/ioextras.rb +43 -2
  117. data/lib/zip/stdrubyext.rb +5 -5
  118. data/lib/zip/tempfile_bugfixed.rb +2 -2
  119. data/lib/zip/zip.rb +618 -149
  120. data/lib/zip/zipfilesystem.rb +59 -8
  121. data/lib/zip/ziprequire.rb +32 -3
  122. data/rwd_files/HowTo_BigTinker.txt +15 -3
  123. data/rwd_files/HowTo_Schedule.txt +265 -0
  124. data/rwd_files/HowTo_Tinker.txt +14 -0
  125. data/rwd_files/Tinkerhelptexthash.txt +5 -2
  126. data/rwd_files/rwdapplications.html +23 -1
  127. data/rwd_files/rwdschedulehelpfiles.txt +19 -0
  128. data/rwd_files/schedules/20050120T09.ics +9 -0
  129. data/rwd_files/schedules/200505may02a.sch +4 -0
  130. data/rwd_files/schedules/Enterprise.ics +411 -0
  131. data/rwd_files/schedules/US Holidays.ics +575 -0
  132. data/rwd_files/schedules/archive/sample.archive +1 -0
  133. data/rwd_files/schedules/testics05.ics +11 -0
  134. data/rwdconfig.dist +4 -2
  135. data/{bigtinker.rb → rwdtinker.rb} +0 -0
  136. data/tests/{makedist-rwdwfoldeditor.rb → makedist-rwdwhypernote.rb} +4 -8
  137. data/tests/makedist.rb +1 -1
  138. data/updates/temp.rb +1 -0
  139. data/zips/rwdwfoldeditor-0.07.zip +0 -0
  140. data/zips/rwdwhypernote-0.16.zip +0 -0
  141. data/zips/rwdwschedule-1.07.zip +0 -0
  142. data/zips/tinkerbellw-0.04.zip +0 -0
  143. metadata +102 -54
  144. data/code/superant.com.foldeditor/0uninstallapplet.rb +0 -17
  145. data/code/superant.com.foldeditor/changehypernotename.rb +0 -21
  146. data/code/superant.com.foldeditor/chooselinkfile.rb +0 -6
  147. data/code/superant.com.foldeditor/choosenotefile.rb +0 -6
  148. data/code/superant.com.foldeditor/clearnotescreen.rb +0 -7
  149. data/code/superant.com.foldeditor/createnewnotehtml.rb +0 -31
  150. data/code/superant.com.foldeditor/hyperlinkcreatelinkfile.rb +0 -19
  151. data/code/superant.com.foldeditor/launchfoldeditorurl.rb +0 -19
  152. data/code/superant.com.foldeditor/listfoldeditorfiles.rb +0 -7
  153. data/code/superant.com.foldeditor/loadconfigurationvariables.rb +0 -14
  154. data/code/superant.com.foldeditor/loadfolddocument.rb +0 -18
  155. data/code/superant.com.foldeditor/loadnextnote.rb +0 -32
  156. data/code/superant.com.foldeditor/loadprevnote.rb +0 -32
  157. data/code/superant.com.foldeditor/loadrwdfoldeditlinkfile.rb +0 -19
  158. data/code/superant.com.foldeditor/openhelpwindowrwdhyernote.rb +0 -30
  159. data/code/superant.com.foldeditor/rwddisplayfoldlinks.rb +0 -41
  160. data/code/superant.com.foldeditor/savefoldlinkfile.rb +0 -20
  161. data/code/superant.com.foldeditor/savehtmlhypernoterecord.rb +0 -20
  162. data/configuration/rwdwfoldeditor.dist +0 -16
  163. data/gui/tinkerbackwindows/superant.com.foldeditor/10appletbegin.rwd +0 -4
  164. data/gui/tinkerbackwindows/superant.com.foldeditor/10aviewnote.rwd +0 -36
  165. data/gui/tinkerbackwindows/superant.com.foldeditor/15htmlview.rwd +0 -43
  166. data/gui/tinkerbackwindows/superant.com.foldeditor/56viewfold.rwd +0 -43
  167. data/gui/tinkerbackwindows/superant.com.foldeditor/67viewconfiguration.rwd +0 -27
  168. data/gui/tinkerbackwindows/superant.com.foldeditor/81jumplinkcommands.rwd +0 -17
  169. data/gui/tinkerbackwindows/superant.com.slideshow/10appletbegin.rwd +0 -4
  170. data/gui/tinkerbackwindows/superant.com.slideshow/11viewnamedata.rwd +0 -19
  171. data/gui/tinkerbackwindows/superant.com.slideshow/13listnamerecordfiles.rwd +0 -21
  172. data/gui/tinkerbackwindows/superant.com.slideshow/16editrecord.rwd +0 -39
  173. data/gui/tinkerbackwindows/superant.com.slideshow/17viewvcardrecord.rwd +0 -32
  174. data/gui/tinkerbackwindows/superant.com.slideshow/18contactutilities.rwd +0 -34
  175. data/gui/tinkerbackwindows/superant.com.slideshow/81jumplinkcommands.rwd +0 -17
  176. data/installed/rwdwfoldeditor.inf +0 -11
  177. data/rwd_files/HowTo_FoldEditor.txt +0 -131
  178. data/rwd_files/default.fld +0 -9
  179. data/rwd_files/rubylinks.fld +0 -6
  180. data/rwd_files/rwdfoldeditorhelpfiles.txt +0 -42
  181. data/rwd_files/rwdhypernote-0.13.fld +0 -202
  182. data/zips/rwdwfoldeditor-0.06.zip +0 -0
  183. data/zips/rwdwschedule-1.05.zip +0 -0
  184. data/zips/tinkerbellw-0.03.zip +0 -0
@@ -0,0 +1,26 @@
1
+ module Icalendar
2
+ # A Timezone is unambiguously defined by the set of time
3
+ # measurement rules determined by the governing body for a given
4
+ # geographic area. These rules describe at a minimum the base offset
5
+ # from UTC for the time zone, often referred to as the Standard Time
6
+ # offset. Many locations adjust their Standard Time forward or backward
7
+ # by one hour, in order to accommodate seasonal changes in number of
8
+ # daylight hours, often referred to as Daylight Saving Time. Some
9
+ # locations adjust their time by a fraction of an hour. Standard Time
10
+ # is also known as Winter Time. Daylight Saving Time is also known as
11
+ # Advanced Time, Summer Time, or Legal Time in certain countries. The
12
+ # following table shows the changes in time zone rules in effect for
13
+ # New York City starting from 1967. Each line represents a description
14
+ # or rule for a particular observance.
15
+ class Timezone < Component
16
+
17
+ def initialize()
18
+ super("VTIMEZONE")
19
+ @components = components
20
+ end
21
+
22
+ def to_s
23
+ super.to_s { |s| s << @components.each { |component| component.to_s } }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module Icalendar
2
+ # A Todo calendar component is a grouping of component
3
+ # properties and possibly Alarm calendar components that represent
4
+ # an action-item or assignment. For example, it can be used to
5
+ # represent an item of work assigned to an individual; such as "turn in
6
+ # travel expense today".
7
+ class Todo < Component
8
+
9
+ attr_reader :alarms
10
+
11
+ def initialize()
12
+ super("VTODO")
13
+
14
+ @alarms = alarms
15
+ end
16
+
17
+ def to_s
18
+ print_string { |s| @alarms.each { |alarm| alarm.to_s } }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,103 @@
1
+ module Icalendar
2
+ # date = date-fullyear date-month date-mday
3
+ # date-fullyear = 4 DIGIT
4
+ # date-month = 2 DIGIT
5
+ # date-mday = 2 DIGIT
6
+ DATE = '(\d\d\d\d)(\d\d)(\d\d)'
7
+
8
+ # time = time-hour [":"] time-minute [":"] time-second [time-secfrac] [time-zone]
9
+ # time-hour = 2 DIGIT
10
+ # time-minute = 2 DIGIT
11
+ # time-second = 2 DIGIT
12
+ # time-secfrac = "," 1*DIGIT
13
+ # time-zone = "Z" / time-numzone
14
+ # time-numzome = sign time-hour [":"] time-minute
15
+ TIME = '(\d\d)(\d\d)(\d\d)(Z)?'
16
+
17
+
18
+ # Maps to dtstart property
19
+ # TODO: Look into having the DateTime library do more of the work...
20
+ module Dtstart
21
+
22
+ # Set the starting DateTime of an Event, Todo,
23
+ # Freebusy or Timezone component. If utc is set to true
24
+ # then this time represents absolute time without regard for
25
+ # timezone information.
26
+ def setStart(start, utc = false)
27
+ if start.respond_to?(:year) # Date format
28
+ s = ""
29
+
30
+ # 4 digit year
31
+ s << start.year.to_s
32
+
33
+ # Double digit month
34
+ s << "0" unless start.month > 9
35
+ s << start.month.to_s
36
+
37
+ # Double digit day
38
+ s << "0" unless start.day > 9
39
+ s << start.day.to_s
40
+ else
41
+ raise InvalidPropertyValue, "Cannot access year on start argument object!"
42
+ end
43
+
44
+ if start.respond_to?(:hour) # include Time format if possible
45
+ s << "T"
46
+
47
+ # Double digit hour
48
+ s << "0" unless start.hour > 9
49
+ s << start.hour.to_s
50
+
51
+ # Double digit minute
52
+ s << "0" unless start.min > 9
53
+ s << start.min.to_s
54
+
55
+ # Double digit second
56
+ s << "0" unless start.sec > 9
57
+ s << start.sec.to_s
58
+ end
59
+
60
+ # UTC time gets a Z suffix
61
+ if utc
62
+ s << "Z"
63
+ end
64
+
65
+ @properties["DTSTART"] = s
66
+ end
67
+
68
+ # Returns the starting DateTime of an Event, Todo, Freebusy or
69
+ # Timezone component.
70
+ def getStart
71
+ # If we don't have a start time then return nil.
72
+ unless @properties.has_key?("DTSTART")
73
+ return nil
74
+ end
75
+ s = @properties["DTSTART"]
76
+
77
+ # If we can't parse the start time figure its bad and return nil.
78
+ unless s =~ %r{#{DATE}}i
79
+ return nil
80
+ end
81
+
82
+ # We can at least create a Date object
83
+ year = $1
84
+ month = $2
85
+ day = $3
86
+
87
+ puts "s: #{s}"
88
+ puts "#{DATE}T#{TIME}"
89
+ # We might be able to get the time too
90
+ if s =~ %r{"#{DATE}T#{TIME}"}i
91
+ hour = $5
92
+ min = $6
93
+ sec = $7
94
+
95
+ puts "Hour: #{hour.to_s}"
96
+
97
+ return DateTime.new(year, month, day, hour, min, sec)
98
+ else
99
+ return Date.new(year.to_i, month.to_i, day.to_i)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,25 @@
1
+ module Icalendar
2
+
3
+ # A property can have attributes associated with it. These "property
4
+ # parameters" contain meta-information about the property or the
5
+ # property value. Property parameters are provided to specify such
6
+ # information as the location of an alternate text representation for a
7
+ # property value, the language of a text property value, the data type
8
+ # of the property value and other attributes.
9
+ class Parameter < Icalendar::Content
10
+
11
+ def to_s
12
+ s = ""
13
+
14
+ s << "#{@name}="
15
+ if is_escapable?
16
+ s << escape(print_value())
17
+ else
18
+ s << print_value
19
+ end
20
+
21
+ s
22
+ end
23
+
24
+ end
25
+ end
data/lib/zip/ioextras.rb CHANGED
@@ -1,4 +1,16 @@
1
- module IOExtras
1
+ module IOExtras #:nodoc:
2
+
3
+ CHUNK_SIZE = 32768
4
+
5
+ RANGE_ALL = 0..-1
6
+
7
+ def self.copy_stream(ostream, istream)
8
+ s = ''
9
+ ostream.write(istream.read(CHUNK_SIZE, s)) until istream.eof?
10
+ end
11
+
12
+
13
+ # Implements kind_of? in order to pretend to be an IO object
2
14
  module FakeIO
3
15
  def kind_of?(object)
4
16
  object == IO || super
@@ -20,6 +32,34 @@ module IOExtras
20
32
 
21
33
  attr_accessor :lineno
22
34
 
35
+ def read(numberOfBytes = nil, buf = nil)
36
+ tbuf = nil
37
+
38
+ if @outputBuffer.length > 0
39
+ if numberOfBytes <= @outputBuffer.length
40
+ tbuf = @outputBuffer.slice!(0, numberOfBytes)
41
+ else
42
+ numberOfBytes -= @outputBuffer.length if (numberOfBytes)
43
+ rbuf = sysread(numberOfBytes, buf)
44
+ tbuf = @outputBuffer
45
+ tbuf << rbuf if (rbuf)
46
+ @outputBuffer = ""
47
+ end
48
+ else
49
+ tbuf = sysread(numberOfBytes, buf)
50
+ end
51
+
52
+ return nil unless (tbuf)
53
+
54
+ if buf
55
+ buf.replace(tbuf)
56
+ else
57
+ buf = tbuf
58
+ end
59
+
60
+ buf
61
+ end
62
+
23
63
  def readlines(aSepString = $/)
24
64
  retVal = []
25
65
  each_line(aSepString) { |line| retVal << line }
@@ -66,7 +106,8 @@ module IOExtras
66
106
  end
67
107
 
68
108
 
69
- #relies on <<
109
+ # Implements many of the output convenience methods of IO.
110
+ # relies on <<
70
111
  module AbstractOutputStream
71
112
  include FakeIO
72
113
 
@@ -16,7 +16,7 @@ module Enumerable #:nodoc:all
16
16
  end
17
17
 
18
18
  unless Object.method_defined?(:object_id)
19
- class Object
19
+ class Object #:nodoc:all
20
20
  # Using object_id which is the new thing, so we need
21
21
  # to make that work in versions prior to 1.8.0
22
22
  alias object_id id
@@ -24,7 +24,7 @@ unless Object.method_defined?(:object_id)
24
24
  end
25
25
 
26
26
  unless File.respond_to?(:read)
27
- class File
27
+ class File # :nodoc:all
28
28
  # singleton method read does not exist in 1.6.x
29
29
  def self.read(fileName)
30
30
  open(fileName) { |f| f.read }
@@ -32,7 +32,7 @@ unless File.respond_to?(:read)
32
32
  end
33
33
  end
34
34
 
35
- class String
35
+ class String #:nodoc:all
36
36
  def starts_with(aString)
37
37
  rindex(aString, 0) == 0
38
38
  end
@@ -50,7 +50,7 @@ class String
50
50
  end
51
51
  end
52
52
 
53
- class Time
53
+ class Time #:nodoc:all
54
54
 
55
55
  #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
56
56
  #
@@ -95,7 +95,7 @@ class Time
95
95
  end
96
96
  end
97
97
 
98
- class Module
98
+ class Module #:nodoc:all
99
99
  def forward_message(forwarder, *messagesToForward)
100
100
  methodDefs = messagesToForward.map {
101
101
  |msg|
@@ -1,13 +1,13 @@
1
1
  #
2
2
  # tempfile - manipulates temporary files
3
3
  #
4
- # $Id: tempfile_bugfixed.rb,v 1.2 2004/03/28 12:46:36 thomas Exp $
4
+ # $Id: tempfile_bugfixed.rb,v 1.2 2005/02/19 20:30:33 thomas Exp $
5
5
  #
6
6
 
7
7
  require 'delegate'
8
8
  require 'tmpdir'
9
9
 
10
- module BugFix
10
+ module BugFix #:nodoc:all
11
11
 
12
12
  # A class for managing temporary files. This library is written to be
13
13
  # thread safe.
data/lib/zip/zip.rb CHANGED
@@ -1,8 +1,8 @@
1
-
2
1
  require 'delegate'
3
2
  require 'singleton'
4
3
  require 'tempfile'
5
4
  require 'ftools'
5
+ require 'stringio'
6
6
  require 'zlib'
7
7
  require 'lib/zip/stdrubyext'
8
8
  require 'lib/zip/ioextras'
@@ -12,7 +12,7 @@ if Tempfile.superclass == SimpleDelegator
12
12
  Tempfile = BugFix::Tempfile
13
13
  end
14
14
 
15
- module Zlib
15
+ module Zlib #:nodoc:all
16
16
  if ! const_defined? :MAX_WBITS
17
17
  MAX_WBITS = Zlib::Deflate.MAX_WBITS
18
18
  end
@@ -20,17 +20,65 @@ end
20
20
 
21
21
  module Zip
22
22
 
23
+ VERSION = '0.9.1'
24
+
23
25
  RUBY_MINOR_VERSION = RUBY_VERSION.split(".")[1].to_i
24
26
 
27
+ RUNNING_ON_WINDOWS = /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
28
+
25
29
  # Ruby 1.7.x compatibility
26
30
  # In ruby 1.6.x and 1.8.0 reading from an empty stream returns
27
31
  # an empty string the first time and then nil.
28
32
  # not so in 1.7.x
29
33
  EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST = RUBY_MINOR_VERSION != 7
30
-
34
+
35
+ # ZipInputStream is the basic class for reading zip entries in a
36
+ # zip file. It is possible to create a ZipInputStream object directly,
37
+ # passing the zip file name to the constructor, but more often than not
38
+ # the ZipInputStream will be obtained from a ZipFile (perhaps using the
39
+ # ZipFileSystem interface) object for a particular entry in the zip
40
+ # archive.
41
+ #
42
+ # A ZipInputStream inherits IOExtras::AbstractInputStream in order
43
+ # to provide an IO-like interface for reading from a single zip
44
+ # entry. Beyond methods for mimicking an IO-object it contains
45
+ # the method get_next_entry for iterating through the entries of
46
+ # an archive. get_next_entry returns a ZipEntry object that describes
47
+ # the zip entry the ZipInputStream is currently reading from.
48
+ #
49
+ # Example that creates a zip archive with ZipOutputStream and reads it
50
+ # back again with a ZipInputStream.
51
+ #
52
+ # require 'zip/zip'
53
+ #
54
+ # Zip::ZipOutputStream::open("my.zip") {
55
+ # |io|
56
+ #
57
+ # io.put_next_entry("first_entry.txt")
58
+ # io.write "Hello world!"
59
+ #
60
+ # io.put_next_entry("adir/first_entry.txt")
61
+ # io.write "Hello again!"
62
+ # }
63
+ #
64
+ #
65
+ # Zip::ZipInputStream::open("my.zip") {
66
+ # |io|
67
+ #
68
+ # while (entry = io.get_next_entry)
69
+ # puts "Contents of #{entry.name}: '#{io.read}'"
70
+ # end
71
+ # }
72
+ #
73
+ # java.util.zip.ZipInputStream is the original inspiration for this
74
+ # class.
75
+
31
76
  class ZipInputStream
32
77
  include IOExtras::AbstractInputStream
33
78
 
79
+ # Opens the indicated zip file. An exception is thrown
80
+ # if the specified offset in the specified filename is
81
+ # not a local zip entry header.
34
82
  def initialize(filename, offset = 0)
35
83
  super()
36
84
  @archiveIO = File.open(filename, "rb")
@@ -42,7 +90,10 @@ module Zip
42
90
  def close
43
91
  @archiveIO.close
44
92
  end
45
-
93
+
94
+ # Same as #initialize but if a block is passed the opened
95
+ # stream is passed to the block and closed when the block
96
+ # returns.
46
97
  def ZipInputStream.open(filename)
47
98
  return new(filename) unless block_given?
48
99
 
@@ -52,12 +103,18 @@ module Zip
52
103
  zio.close if zio
53
104
  end
54
105
 
106
+ # Returns a ZipEntry object. It is necessary to call this
107
+ # method on a newly created ZipInputStream before reading from
108
+ # the first entry in the archive. Returns nil when there are
109
+ # no more entries.
110
+
55
111
  def get_next_entry
56
112
  @archiveIO.seek(@currentEntry.next_header_offset,
57
- IO::SEEK_SET) if @currentEntry
113
+ IO::SEEK_SET) if @currentEntry
58
114
  open_entry
59
115
  end
60
116
 
117
+ # Rewinds the stream to the beginning of the current entry
61
118
  def rewind
62
119
  return if @currentEntry.nil?
63
120
  @lineno = 0
@@ -66,6 +123,18 @@ module Zip
66
123
  open_entry
67
124
  end
68
125
 
126
+ # Modeled after IO.sysread
127
+ def sysread(numberOfBytes = nil, buf = nil)
128
+ @decompressor.sysread(numberOfBytes, buf)
129
+ end
130
+
131
+ def eof
132
+ @outputBuffer.empty? && @decompressor.eof
133
+ end
134
+ alias :eof? :eof
135
+
136
+ protected
137
+
69
138
  def open_entry
70
139
  @currentEntry = ZipEntry.read_local_entry(@archiveIO)
71
140
  if (@currentEntry == nil)
@@ -83,14 +152,10 @@ module Zip
83
152
  return @currentEntry
84
153
  end
85
154
 
86
- def read(numberOfBytes = nil)
87
- @decompressor.read(numberOfBytes)
88
- end
89
- protected
90
155
  def produce_input
91
156
  @decompressor.produce_input
92
157
  end
93
-
158
+
94
159
  def input_finished?
95
160
  @decompressor.input_finished?
96
161
  end
@@ -114,11 +179,11 @@ module Zip
114
179
  @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST
115
180
  end
116
181
 
117
- def read(numberOfBytes = nil)
182
+ def sysread(numberOfBytes = nil, buf = nil)
118
183
  readEverything = (numberOfBytes == nil)
119
184
  while (readEverything || @outputBuffer.length < numberOfBytes)
120
185
  break if internal_input_finished?
121
- @outputBuffer << internal_produce_input
186
+ @outputBuffer << internal_produce_input(buf)
122
187
  end
123
188
  return value_when_finished if @outputBuffer.length==0 && input_finished?
124
189
  endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes
@@ -134,14 +199,24 @@ module Zip
134
199
  end
135
200
 
136
201
  # to be used with produce_input, not read (as read may still have more data cached)
202
+ # is data cached anywhere other than @outputBuffer? the comment above may be wrong
137
203
  def input_finished?
138
204
  @outputBuffer.empty? && internal_input_finished?
139
205
  end
206
+ alias :eof :input_finished?
207
+ alias :eof? :input_finished?
140
208
 
141
209
  private
142
210
 
143
- def internal_produce_input
144
- @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE))
211
+ def internal_produce_input(buf = nil)
212
+ retried = 0
213
+ begin
214
+ @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE, buf))
215
+ rescue Zlib::BufError
216
+ raise if (retried >= 5) # how many times should we retry?
217
+ retried += 1
218
+ retry
219
+ end
145
220
  end
146
221
 
147
222
  def internal_input_finished?
@@ -165,7 +240,7 @@ module Zip
165
240
  end
166
241
 
167
242
  # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ?
168
- def read(numberOfBytes = nil)
243
+ def sysread(numberOfBytes = nil, buf = nil)
169
244
  if input_finished?
170
245
  hasReturnedEmptyStringVal=@hasReturnedEmptyString
171
246
  @hasReturnedEmptyString=true
@@ -177,21 +252,23 @@ module Zip
177
252
  numberOfBytes = @charsToRead-@readSoFar
178
253
  end
179
254
  @readSoFar += numberOfBytes
180
- @inputStream.read(numberOfBytes)
255
+ @inputStream.read(numberOfBytes, buf)
181
256
  end
182
257
 
183
258
  def produce_input
184
- read(Decompressor::CHUNK_SIZE)
259
+ sysread(Decompressor::CHUNK_SIZE)
185
260
  end
186
261
 
187
262
  def input_finished?
188
263
  (@readSoFar >= @charsToRead)
189
264
  end
265
+ alias :eof :input_finished?
266
+ alias :eof? :input_finished?
190
267
  end
191
268
 
192
269
  class NullDecompressor #:nodoc:all
193
270
  include Singleton
194
- def read(numberOfBytes = nil)
271
+ def sysread(numberOfBytes = nil, buf = nil)
195
272
  nil
196
273
  end
197
274
 
@@ -202,6 +279,11 @@ module Zip
202
279
  def input_finished?
203
280
  true
204
281
  end
282
+
283
+ def eof
284
+ true
285
+ end
286
+ alias :eof? :eof
205
287
  end
206
288
 
207
289
  class NullInputStream < NullDecompressor #:nodoc:all
@@ -211,9 +293,61 @@ module Zip
211
293
  class ZipEntry
212
294
  STORED = 0
213
295
  DEFLATED = 8
296
+
297
+ FSTYPE_FAT = 0
298
+ FSTYPE_AMIGA = 1
299
+ FSTYPE_VMS = 2
300
+ FSTYPE_UNIX = 3
301
+ FSTYPE_VM_CMS = 4
302
+ FSTYPE_ATARI = 5
303
+ FSTYPE_HPFS = 6
304
+ FSTYPE_MAC = 7
305
+ FSTYPE_Z_SYSTEM = 8
306
+ FSTYPE_CPM = 9
307
+ FSTYPE_TOPS20 = 10
308
+ FSTYPE_NTFS = 11
309
+ FSTYPE_QDOS = 12
310
+ FSTYPE_ACORN = 13
311
+ FSTYPE_VFAT = 14
312
+ FSTYPE_MVS = 15
313
+ FSTYPE_BEOS = 16
314
+ FSTYPE_TANDEM = 17
315
+ FSTYPE_THEOS = 18
316
+ FSTYPE_MAC_OSX = 19
317
+ FSTYPE_ATHEOS = 30
318
+
319
+ FSTYPES = {
320
+ FSTYPE_FAT => 'FAT'.freeze,
321
+ FSTYPE_AMIGA => 'Amiga'.freeze,
322
+ FSTYPE_VMS => 'VMS (Vax or Alpha AXP)'.freeze,
323
+ FSTYPE_UNIX => 'Unix'.freeze,
324
+ FSTYPE_VM_CMS => 'VM/CMS'.freeze,
325
+ FSTYPE_ATARI => 'Atari ST'.freeze,
326
+ FSTYPE_HPFS => 'OS/2 or NT HPFS'.freeze,
327
+ FSTYPE_MAC => 'Macintosh'.freeze,
328
+ FSTYPE_Z_SYSTEM => 'Z-System'.freeze,
329
+ FSTYPE_CPM => 'CP/M'.freeze,
330
+ FSTYPE_TOPS20 => 'TOPS-20'.freeze,
331
+ FSTYPE_NTFS => 'NTFS'.freeze,
332
+ FSTYPE_QDOS => 'SMS/QDOS'.freeze,
333
+ FSTYPE_ACORN => 'Acorn RISC OS'.freeze,
334
+ FSTYPE_VFAT => 'Win32 VFAT'.freeze,
335
+ FSTYPE_MVS => 'MVS'.freeze,
336
+ FSTYPE_BEOS => 'BeOS'.freeze,
337
+ FSTYPE_TANDEM => 'Tandem NSK'.freeze,
338
+ FSTYPE_THEOS => 'Theos'.freeze,
339
+ FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze,
340
+ FSTYPE_ATHEOS => 'AtheOS'.freeze,
341
+ }.freeze
214
342
 
215
343
  attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method,
216
- :name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes
344
+ :name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes, :gp_flags, :header_signature
345
+
346
+ attr_accessor :follow_symlinks
347
+ attr_accessor :restore_times, :restore_permissions, :restore_ownership
348
+ attr_accessor :unix_uid, :unix_gid, :unix_perms
349
+
350
+ attr_reader :ftype, :filepath # :nodoc:
217
351
 
218
352
  def initialize(zipfile = "", name = "", comment = "", extra = "",
219
353
  compressed_size = 0, crc = 0,
@@ -227,11 +361,37 @@ module Zip
227
361
  @internalFileAttributes = 1
228
362
  @externalFileAttributes = 0
229
363
  @version = 52 # this library's version
230
- @fstype = 0 # default is fat
364
+ @ftype = nil # unspecified or unknown
365
+ @filepath = nil
366
+ if Zip::RUNNING_ON_WINDOWS
367
+ @fstype = FSTYPE_FAT
368
+ else
369
+ @fstype = FSTYPE_UNIX
370
+ end
231
371
  @zipfile, @comment, @compressed_size, @crc, @extra, @compression_method,
232
372
  @name, @size = zipfile, comment, compressed_size, crc,
233
373
  extra, compression_method, name, size
234
374
  @time = time
375
+
376
+ @follow_symlinks = false
377
+
378
+ @restore_times = true
379
+ @restore_permissions = false
380
+ @restore_ownership = false
381
+
382
+ # BUG: need an extra field to support uid/gid's
383
+ @unix_uid = nil
384
+ @unix_gid = nil
385
+ @unix_perms = nil
386
+ # @posix_acl = nil
387
+ # @ntfs_acl = nil
388
+
389
+ if name_is_directory?
390
+ @ftype = :directory
391
+ else
392
+ @ftype = :file
393
+ end
394
+
235
395
  unless ZipExtraField === @extra
236
396
  @extra = ZipExtraField.new(@extra.to_s)
237
397
  end
@@ -256,13 +416,27 @@ module Zip
256
416
  @time = aTime
257
417
  end
258
418
 
419
+ # Returns +true+ if the entry is a directory.
259
420
  def directory?
260
- return (%r{\/$} =~ @name) != nil
421
+ raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
422
+ @ftype == :directory
261
423
  end
262
424
  alias :is_directory :directory?
263
425
 
426
+ # Returns +true+ if the entry is a file.
264
427
  def file?
265
- ! directory?
428
+ raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
429
+ @ftype == :file
430
+ end
431
+
432
+ # Returns +true+ if the entry is a symlink.
433
+ def symlink?
434
+ raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype
435
+ @ftype == :link
436
+ end
437
+
438
+ def name_is_directory? #:nodoc:all
439
+ (%r{\/$} =~ @name) != nil
266
440
  end
267
441
 
268
442
  def local_entry_offset #:nodoc:all
@@ -281,24 +455,42 @@ module Zip
281
455
  def next_header_offset #:nodoc:all
282
456
  local_entry_offset + self.compressed_size
283
457
  end
284
-
458
+
459
+ # Extracts entry to file destPath (defaults to @name).
460
+ def extract(destPath = @name, &onExistsProc)
461
+ onExistsProc ||= proc { false }
462
+
463
+ if directory?
464
+ create_directory(destPath, &onExistsProc)
465
+ elsif file?
466
+ write_file(destPath, &onExistsProc)
467
+ elsif symlink?
468
+ create_symlink(destPath, &onExistsProc)
469
+ else
470
+ raise RuntimeError, "unknown file type #{self.inspect}"
471
+ end
472
+
473
+ self
474
+ end
475
+
285
476
  def to_s
286
477
  @name
287
478
  end
288
479
 
289
480
  protected
290
481
 
291
- def ZipEntry.read_zip_short(io)
482
+ def ZipEntry.read_zip_short(io) # :nodoc:
292
483
  io.read(2).unpack('v')[0]
293
484
  end
294
485
 
295
- def ZipEntry.read_zip_long(io)
486
+ def ZipEntry.read_zip_long(io) # :nodoc:
296
487
  io.read(4).unpack('V')[0]
297
488
  end
298
489
  public
299
490
 
300
491
  LOCAL_ENTRY_SIGNATURE = 0x04034b50
301
492
  LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30
493
+ LOCAL_ENTRY_TRAILING_DESCRIPTOR_LENGTH = 4+4+4
302
494
 
303
495
  def read_local_entry(io) #:nodoc:all
304
496
  @localHeaderOffset = io.tell
@@ -307,10 +499,10 @@ module Zip
307
499
  raise ZipError, "Premature end of file. Not enough data for zip entry local header"
308
500
  end
309
501
 
310
- localHeader ,
502
+ @header_signature ,
311
503
  @version ,
312
504
  @fstype ,
313
- @gpFlags ,
505
+ @gp_flags ,
314
506
  @compression_method,
315
507
  lastModTime ,
316
508
  lastModDate ,
@@ -320,7 +512,7 @@ module Zip
320
512
  nameLength ,
321
513
  extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv')
322
514
 
323
- unless (localHeader == LOCAL_ENTRY_SIGNATURE)
515
+ unless (@header_signature == LOCAL_ENTRY_SIGNATURE)
324
516
  raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
325
517
  end
326
518
  set_time(lastModDate, lastModTime)
@@ -353,7 +545,7 @@ module Zip
353
545
  io <<
354
546
  [LOCAL_ENTRY_SIGNATURE ,
355
547
  0 ,
356
- 0 , # @gpFlags ,
548
+ 0 , # @gp_flags ,
357
549
  @compression_method ,
358
550
  @time.to_binary_dos_time , # @lastModTime ,
359
551
  @time.to_binary_dos_date , # @lastModDate ,
@@ -374,12 +566,12 @@ module Zip
374
566
  unless (staticSizedFieldsBuf.size == CDIR_ENTRY_STATIC_HEADER_LENGTH)
375
567
  raise ZipError, "Premature end of file. Not enough data for zip cdir entry header"
376
568
  end
377
-
378
- cdirSignature ,
569
+
570
+ @header_signature ,
379
571
  @version , # version of encoding software
380
572
  @fstype , # filesystem type
381
573
  @versionNeededToExtract,
382
- @gpFlags ,
574
+ @gp_flags ,
383
575
  @compression_method ,
384
576
  lastModTime ,
385
577
  lastModDate ,
@@ -397,7 +589,7 @@ module Zip
397
589
  @extra ,
398
590
  @comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV')
399
591
 
400
- unless (cdirSignature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
592
+ unless (@header_signature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
401
593
  raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'"
402
594
  end
403
595
  set_time(lastModDate, lastModTime)
@@ -412,6 +604,28 @@ module Zip
412
604
  unless (@comment && @comment.length == commentLength)
413
605
  raise ZipError, "Truncated cdir zip entry header"
414
606
  end
607
+
608
+ case @fstype
609
+ when FSTYPE_UNIX
610
+ @unix_perms = (@externalFileAttributes >> 16) & 07777
611
+
612
+ case (@externalFileAttributes >> 28)
613
+ when 04
614
+ @ftype = :directory
615
+ when 010
616
+ @ftype = :file
617
+ when 012
618
+ @ftype = :link
619
+ else
620
+ raise ZipInternalError, "unknown file type #{'0%o' % (@externalFileAttributes >> 28)}"
621
+ end
622
+ else
623
+ if name_is_directory?
624
+ @ftype = :directory
625
+ else
626
+ @ftype = :file
627
+ end
628
+ end
415
629
  end
416
630
 
417
631
  def ZipEntry.read_c_dir_entry(io) #:nodoc:all
@@ -422,14 +636,65 @@ module Zip
422
636
  return nil
423
637
  end
424
638
 
639
+ def file_stat(path) # :nodoc:
640
+ if @follow_symlinks
641
+ return File::stat(path)
642
+ else
643
+ return File::lstat(path)
644
+ end
645
+ end
646
+
647
+ def get_extra_attributes_from_path(path) # :nodoc:
648
+ unless Zip::RUNNING_ON_WINDOWS
649
+ stat = file_stat(path)
650
+ @unix_uid = stat.uid
651
+ @unix_gid = stat.gid
652
+ @unix_perms = stat.mode & 07777
653
+ end
654
+ end
655
+
656
+ def set_extra_attributes_on_path(destPath) # :nodoc:
657
+ return unless (file? or directory?)
658
+
659
+ case @fstype
660
+ when FSTYPE_UNIX
661
+ # BUG: does not update timestamps into account
662
+ # ignore setuid/setgid bits by default. honor if @restore_ownership
663
+ unix_perms_mask = 01777
664
+ unix_perms_mask = 07777 if (@restore_ownership)
665
+ File::chmod(@unix_perms & unix_perms_mask, destPath) if (@restore_permissions && @unix_perms)
666
+ File::chown(@unix_uid, @unix_gid, destPath) if (@restore_ownership && @unix_uid && @unix_gid && Process::egid == 0)
667
+ # File::utimes()
668
+ end
669
+ end
425
670
 
426
671
  def write_c_dir_entry(io) #:nodoc:all
672
+ case @fstype
673
+ when FSTYPE_UNIX
674
+ ft = nil
675
+ case @ftype
676
+ when :file
677
+ ft = 010
678
+ @unix_perms ||= 0644
679
+ when :directory
680
+ ft = 004
681
+ @unix_perms ||= 0755
682
+ when :symlink
683
+ ft = 012
684
+ @unix_perms ||= 0755
685
+ else
686
+ raise ZipInternalError, "unknown file type #{self.inspect}"
687
+ end
688
+
689
+ @externalFileAttributes = (ft << 12 | (@unix_perms & 07777)) << 16
690
+ end
691
+
427
692
  io <<
428
693
  [CENTRAL_DIRECTORY_ENTRY_SIGNATURE,
429
694
  @version , # version of encoding software
430
695
  @fstype , # filesystem type
431
696
  0 , # @versionNeededToExtract ,
432
- 0 , # @gpFlags ,
697
+ 0 , # @gp_flags ,
433
698
  @compression_method ,
434
699
  @time.to_binary_dos_time , # @lastModTime ,
435
700
  @time.to_binary_dos_date , # @lastModDate ,
@@ -453,14 +718,15 @@ module Zip
453
718
  end
454
719
 
455
720
  def == (other)
456
- return false unless other.class == ZipEntry
721
+ return false unless other.class == self.class
457
722
  # Compares contents of local entry and exposed fields
458
723
  (@compression_method == other.compression_method &&
459
724
  @crc == other.crc &&
460
- @compressed_size == other.compressed_size &&
725
+ @compressed_size == other.compressed_size &&
461
726
  @size == other.size &&
462
727
  @name == other.name &&
463
728
  @extra == other.extra &&
729
+ @filepath == other.filepath &&
464
730
  self.time.dos_equals(other.time))
465
731
  end
466
732
 
@@ -468,23 +734,79 @@ module Zip
468
734
  return to_s <=> other.to_s
469
735
  end
470
736
 
471
- def get_input_stream
472
- zis = ZipInputStream.new(@zipfile, localHeaderOffset)
473
- zis.get_next_entry
474
- if block_given?
475
- begin
476
- return yield(zis)
477
- ensure
478
- zis.close
479
- end
737
+ # Returns an IO like object for the given ZipEntry.
738
+ # Warning: may behave weird with symlinks.
739
+ def get_input_stream(&aProc)
740
+ if @ftype == :directory
741
+ return yield(NullInputStream.instance) if block_given?
742
+ return NullInputStream.instance
743
+ elsif @filepath
744
+ case @ftype
745
+ when :file
746
+ return File.open(@filepath, "rb", &aProc)
747
+
748
+ when :symlink
749
+ linkpath = File::readlink(@filepath)
750
+ stringio = StringIO.new(linkpath)
751
+ return yield(stringio) if block_given?
752
+ return stringio
753
+ else
754
+ raise "unknown @ftype #{@ftype}"
755
+ end
480
756
  else
481
- return zis
757
+ zis = ZipInputStream.new(@zipfile, localHeaderOffset)
758
+ zis.get_next_entry
759
+ if block_given?
760
+ begin
761
+ return yield(zis)
762
+ ensure
763
+ zis.close
764
+ end
765
+ else
766
+ return zis
767
+ end
482
768
  end
483
769
  end
484
770
 
771
+ def gather_fileinfo_from_srcpath(srcPath) # :nodoc:
772
+ stat = file_stat(srcPath)
773
+ case stat.ftype
774
+ when 'file'
775
+ if name_is_directory?
776
+ raise ArgumentError,
777
+ "entry name '#{newEntry}' indicates directory entry, but "+
778
+ "'#{srcPath}' is not a directory"
779
+ end
780
+ @ftype = :file
781
+ when 'directory'
782
+ if ! name_is_directory?
783
+ @name += "/"
784
+ end
785
+ @ftype = :directory
786
+ when 'link'
787
+ if name_is_directory?
788
+ raise ArgumentError,
789
+ "entry name '#{newEntry}' indicates directory entry, but "+
790
+ "'#{srcPath}' is not a directory"
791
+ end
792
+ @ftype = :symlink
793
+ else
794
+ raise RuntimeError, "unknown file type: #{srcPath.inspect} #{stat.inspect}"
795
+ end
796
+
797
+ @filepath = srcPath
798
+ get_extra_attributes_from_path(@filepath)
799
+ end
485
800
 
486
801
  def write_to_zip_output_stream(aZipOutputStream) #:nodoc:all
487
- aZipOutputStream.copy_raw_entry(self)
802
+ if @ftype == :directory
803
+ aZipOutputStream.put_next_entry(self)
804
+ elsif @filepath
805
+ aZipOutputStream.put_next_entry(self)
806
+ get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) }
807
+ else
808
+ aZipOutputStream.copy_raw_entry(self)
809
+ end
488
810
  end
489
811
 
490
812
  def parent_as_string
@@ -498,19 +820,103 @@ module Zip
498
820
  end
499
821
 
500
822
  private
823
+
501
824
  def set_time(binaryDosDate, binaryDosTime)
502
825
  @time = Time.parse_binary_dos_format(binaryDosDate, binaryDosTime)
503
826
  rescue ArgumentError
504
827
  puts "Invalid date/time in zip entry"
505
828
  end
829
+
830
+ def write_file(destPath, continueOnExistsProc = proc { false })
831
+ if File.exists?(destPath) && ! yield(self, destPath)
832
+ raise ZipDestinationFileExistsError,
833
+ "Destination '#{destPath}' already exists"
834
+ end
835
+ File.open(destPath, "wb") do |os|
836
+ get_input_stream do |is|
837
+ set_extra_attributes_on_path(destPath)
838
+
839
+ buf = ''
840
+ while buf = is.sysread(Decompressor::CHUNK_SIZE, buf)
841
+ os << buf
842
+ end
843
+ end
844
+ end
845
+ end
846
+
847
+ def create_directory(destPath)
848
+ if File.directory? destPath
849
+ return
850
+ elsif File.exists? destPath
851
+ if block_given? && yield(self, destPath)
852
+ File.rm_f destPath
853
+ else
854
+ raise ZipDestinationFileExistsError,
855
+ "Cannot create directory '#{destPath}'. "+
856
+ "A file already exists with that name"
857
+ end
858
+ end
859
+ Dir.mkdir destPath
860
+ set_extra_attributes_on_path(destPath)
861
+ end
862
+
863
+ # BUG: create_symlink() does not use &onExistsProc
864
+ def create_symlink(destPath)
865
+ stat = nil
866
+ begin
867
+ stat = File::lstat(destPath)
868
+ rescue Errno::ENOENT
869
+ end
870
+
871
+ io = get_input_stream
872
+ linkto = io.read
873
+
874
+ if stat
875
+ if stat.symlink?
876
+ if File::readlink(destPath) == linkto
877
+ return
878
+ else
879
+ raise ZipDestinationFileExistsError,
880
+ "Cannot create symlink '#{destPath}'. "+
881
+ "A symlink already exists with that name"
882
+ end
883
+ else
884
+ raise ZipDestinationFileExistsError,
885
+ "Cannot create symlink '#{destPath}'. "+
886
+ "A file already exists with that name"
887
+ end
888
+ end
889
+
890
+ File::symlink(linkto, destPath)
891
+ end
506
892
  end
507
893
 
508
894
 
895
+ # ZipOutputStream is the basic class for writing zip files. It is
896
+ # possible to create a ZipOutputStream object directly, passing
897
+ # the zip file name to the constructor, but more often than not
898
+ # the ZipOutputStream will be obtained from a ZipFile (perhaps using the
899
+ # ZipFileSystem interface) object for a particular entry in the zip
900
+ # archive.
901
+ #
902
+ # A ZipOutputStream inherits IOExtras::AbstractOutputStream in order
903
+ # to provide an IO-like interface for writing to a single zip
904
+ # entry. Beyond methods for mimicking an IO-object it contains
905
+ # the method put_next_entry that closes the current entry
906
+ # and creates a new.
907
+ #
908
+ # Please refer to ZipInputStream for example code.
909
+ #
910
+ # java.util.zip.ZipOutputStream is the original inspiration for this
911
+ # class.
912
+
509
913
  class ZipOutputStream
510
914
  include IOExtras::AbstractOutputStream
511
915
 
512
916
  attr_accessor :comment
513
917
 
918
+ # Opens the indicated zip file. If a file with that name already
919
+ # exists it will be overwritten.
514
920
  def initialize(fileName)
515
921
  super()
516
922
  @fileName = fileName
@@ -522,6 +928,9 @@ module Zip
522
928
  @comment = nil
523
929
  end
524
930
 
931
+ # Same as #initialize but if a block is passed the opened
932
+ # stream is passed to the block and closed when the block
933
+ # returns.
525
934
  def ZipOutputStream.open(fileName)
526
935
  return new(fileName) unless block_given?
527
936
  zos = new(fileName)
@@ -530,6 +939,7 @@ module Zip
530
939
  zos.close if zos
531
940
  end
532
941
 
942
+ # Closes the stream and writes the central directory to the zip file
533
943
  def close
534
944
  return if @closed
535
945
  finalize_current_entry
@@ -539,10 +949,12 @@ module Zip
539
949
  @closed = true
540
950
  end
541
951
 
952
+ # Closes the current entry and opens a new for writing.
953
+ # +entry+ can be a ZipEntry object or a string.
542
954
  def put_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION)
543
955
  raise ZipError, "zip stream is closed" if @closed
544
956
  newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@fileName, entry.to_s)
545
- init_next_entry(newEntry)
957
+ init_next_entry(newEntry, level)
546
958
  @currentEntry=newEntry
547
959
  end
548
960
 
@@ -614,6 +1026,7 @@ module Zip
614
1026
  end
615
1027
 
616
1028
  public
1029
+ # Modeled after IO.<<
617
1030
  def << (data)
618
1031
  @compressor << data
619
1032
  end
@@ -679,7 +1092,7 @@ module Zip
679
1092
  end
680
1093
 
681
1094
 
682
- class ZipEntrySet
1095
+ class ZipEntrySet #:nodoc:all
683
1096
  include Enumerable
684
1097
 
685
1098
  def initialize(anEnumerable = [])
@@ -728,13 +1141,20 @@ module Zip
728
1141
  @entrySet[entry.parent_as_string]
729
1142
  end
730
1143
 
1144
+ def glob(pattern, flags = File::FNM_PATHNAME|File::FNM_DOTMATCH)
1145
+ entries.select {
1146
+ |entry|
1147
+ File.fnmatch(pattern, entry.name.chomp('/'), flags)
1148
+ }
1149
+ end
1150
+
731
1151
  #TODO attr_accessor :auto_create_directories
732
1152
  protected
733
1153
  attr_accessor :entrySet
734
1154
  end
735
1155
 
736
1156
 
737
- class ZipCentralDirectory #:nodoc:all
1157
+ class ZipCentralDirectory
738
1158
  include Enumerable
739
1159
 
740
1160
  END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50
@@ -742,24 +1162,25 @@ module Zip
742
1162
  STATIC_EOCD_SIZE = 22
743
1163
 
744
1164
  attr_reader :comment
745
-
1165
+
1166
+ # Returns an Enumerable containing the entries.
746
1167
  def entries
747
1168
  @entrySet.entries
748
1169
  end
749
1170
 
750
- def initialize(entries = ZipEntrySet.new, comment = "")
1171
+ def initialize(entries = ZipEntrySet.new, comment = "") #:nodoc:
751
1172
  super()
752
1173
  @entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries)
753
1174
  @comment = comment
754
1175
  end
755
1176
 
756
- def write_to_stream(io)
1177
+ def write_to_stream(io) #:nodoc:
757
1178
  offset = io.tell
758
1179
  @entrySet.each { |entry| entry.write_c_dir_entry(io) }
759
1180
  write_e_o_c_d(io, offset)
760
1181
  end
761
1182
 
762
- def write_e_o_c_d(io, offset)
1183
+ def write_e_o_c_d(io, offset) #:nodoc:
763
1184
  io <<
764
1185
  [END_OF_CENTRAL_DIRECTORY_SIGNATURE,
765
1186
  0 , # @numberOfThisDisk
@@ -773,13 +1194,13 @@ module Zip
773
1194
  end
774
1195
  private :write_e_o_c_d
775
1196
 
776
- def cdir_size
1197
+ def cdir_size #:nodoc:
777
1198
  # does not include eocd
778
1199
  @entrySet.inject(0) { |value, entry| entry.cdir_header_size + value }
779
1200
  end
780
1201
  private :cdir_size
781
1202
 
782
- def read_e_o_c_d(io)
1203
+ def read_e_o_c_d(io) #:nodoc:
783
1204
  buf = get_e_o_c_d(io)
784
1205
  @numberOfThisDisk = ZipEntry::read_zip_short(buf)
785
1206
  @numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf)
@@ -792,7 +1213,7 @@ module Zip
792
1213
  raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0
793
1214
  end
794
1215
 
795
- def read_central_directory_entries(io)
1216
+ def read_central_directory_entries(io) #:nodoc:
796
1217
  begin
797
1218
  io.seek(@cdirOffset, IO::SEEK_SET)
798
1219
  rescue Errno::EINVAL
@@ -804,20 +1225,33 @@ module Zip
804
1225
  }
805
1226
  end
806
1227
 
807
- def read_from_stream(io)
1228
+ def read_from_stream(io) #:nodoc:
808
1229
  read_e_o_c_d(io)
809
1230
  read_central_directory_entries(io)
810
1231
  end
811
1232
 
812
- def get_e_o_c_d(io)
1233
+ def get_e_o_c_d(io) #:nodoc:
813
1234
  begin
814
1235
  io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END)
815
1236
  rescue Errno::EINVAL
816
1237
  io.seek(0, IO::SEEK_SET)
817
- rescue Errno::EFBIG # FreeBSD 4.9 returns Errno::EFBIG instead of Errno::EINVAL
1238
+ rescue Errno::EFBIG # FreeBSD 4.9 raise Errno::EFBIG instead of Errno::EINVAL
818
1239
  io.seek(0, IO::SEEK_SET)
819
1240
  end
820
- buf = io.read
1241
+
1242
+ # 'buf = io.read' substituted with lump of code to work around FreeBSD 4.5 issue
1243
+ retried = false
1244
+ buf = nil
1245
+ begin
1246
+ buf = io.read
1247
+ rescue Errno::EFBIG # FreeBSD 4.5 may raise Errno::EFBIG
1248
+ raise if (retried)
1249
+ retried = true
1250
+
1251
+ io.seek(0, IO::SEEK_SET)
1252
+ retry
1253
+ end
1254
+
821
1255
  sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V'))
822
1256
  raise ZipError, "Zip end of central directory signature not found" unless sigIndex
823
1257
  buf=buf.slice!((sigIndex+4)...(buf.size))
@@ -826,16 +1260,19 @@ module Zip
826
1260
  end
827
1261
  return buf
828
1262
  end
829
-
1263
+
1264
+ # For iterating over the entries.
830
1265
  def each(&proc)
831
1266
  @entrySet.each(&proc)
832
1267
  end
833
1268
 
1269
+ # Returns the number of entries in the central directory (and
1270
+ # consequently in the zip archive).
834
1271
  def size
835
1272
  @entrySet.size
836
1273
  end
837
1274
 
838
- def ZipCentralDirectory.read_from_stream(io)
1275
+ def ZipCentralDirectory.read_from_stream(io) #:nodoc:
839
1276
  cdir = new
840
1277
  cdir.read_from_stream(io)
841
1278
  return cdir
@@ -843,7 +1280,7 @@ module Zip
843
1280
  return nil
844
1281
  end
845
1282
 
846
- def == (other)
1283
+ def == (other) #:nodoc:
847
1284
  return false unless other.kind_of?(ZipCentralDirectory)
848
1285
  @entrySet.entries.sort == other.entries.sort && comment == other.comment
849
1286
  end
@@ -856,28 +1293,88 @@ module Zip
856
1293
  class ZipDestinationFileExistsError < ZipError; end
857
1294
  class ZipCompressionMethodError < ZipError; end
858
1295
  class ZipEntryNameError < ZipError; end
859
-
1296
+ class ZipInternalError < ZipError; end
1297
+
1298
+ # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK.
1299
+ # The most important methods are those inherited from
1300
+ # ZipCentralDirectory for accessing information about the entries in
1301
+ # the archive and methods such as get_input_stream and
1302
+ # get_output_stream for reading from and writing entries to the
1303
+ # archive. The class includes a few convenience methods such as
1304
+ # #extract for extracting entries to the filesystem, and #remove,
1305
+ # #replace, #rename and #mkdir for making simple modifications to
1306
+ # the archive.
1307
+ #
1308
+ # Modifications to a zip archive are not committed until #commit or
1309
+ # #close is called. The method #open accepts a block following
1310
+ # the pattern from File.open offering a simple way to
1311
+ # automatically close the archive when the block returns.
1312
+ #
1313
+ # The following example opens zip archive <code>my.zip</code>
1314
+ # (creating it if it doesn't exist) and adds an entry
1315
+ # <code>first.txt</code> and a directory entry <code>a_dir</code>
1316
+ # to it.
1317
+ #
1318
+ # require 'zip/zip'
1319
+ #
1320
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
1321
+ # |zipfile|
1322
+ # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" }
1323
+ # zipfile.mkdir("a_dir")
1324
+ # }
1325
+ #
1326
+ # The next example reopens <code>my.zip</code> writes the contents of
1327
+ # <code>first.txt</code> to standard out and deletes the entry from
1328
+ # the archive.
1329
+ #
1330
+ # require 'zip/zip'
1331
+ #
1332
+ # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
1333
+ # |zipfile|
1334
+ # puts zipfile.read("first.txt")
1335
+ # zipfile.remove("first.txt")
1336
+ # }
1337
+ #
1338
+ # ZipFileSystem offers an alternative API that emulates ruby's
1339
+ # interface for accessing the filesystem, ie. the File and Dir classes.
1340
+
860
1341
  class ZipFile < ZipCentralDirectory
861
1342
 
862
1343
  CREATE = 1
863
1344
 
864
1345
  attr_reader :name
865
1346
 
1347
+ # default -> false
1348
+ attr_accessor :restore_ownership
1349
+ # default -> false
1350
+ attr_accessor :restore_permissions
1351
+ # default -> true
1352
+ attr_accessor :restore_times
1353
+
1354
+ # Opens a zip archive. Pass true as the second parameter to create
1355
+ # a new archive if it doesn't exist already.
866
1356
  def initialize(fileName, create = nil)
867
1357
  super()
868
1358
  @name = fileName
869
1359
  @comment = ""
870
1360
  if (File.exists?(fileName))
871
1361
  File.open(name, "rb") { |f| read_from_stream(f) }
872
- elsif (create == ZipFile::CREATE)
1362
+ elsif (create)
873
1363
  @entrySet = ZipEntrySet.new
874
1364
  else
875
1365
  raise ZipError, "File #{fileName} not found"
876
1366
  end
877
1367
  @create = create
878
1368
  @storedEntries = @entrySet.dup
1369
+
1370
+ @restore_ownership = false
1371
+ @restore_permissions = false
1372
+ @restore_times = true
879
1373
  end
880
-
1374
+
1375
+ # Same as #new. If a block is passed the ZipFile object is passed
1376
+ # to the block and is automatically closed afterwards just as with
1377
+ # ruby's builtin File.open method.
881
1378
  def ZipFile.open(fileName, create = nil)
882
1379
  zf = ZipFile.new(fileName, create)
883
1380
  if block_given?
@@ -891,8 +1388,15 @@ module Zip
891
1388
  end
892
1389
  end
893
1390
 
1391
+ # Returns the zip files comment, if it has one
894
1392
  attr_accessor :comment
895
1393
 
1394
+ # Iterates over the contents of the ZipFile. This is more efficient
1395
+ # than using a ZipInputStream since this methods simply iterates
1396
+ # through the entries in the central directory structure in the archive
1397
+ # whereas ZipInputStream jumps through the entire archive accessing the
1398
+ # local entry headers (which contain the same information as the
1399
+ # central directory).
896
1400
  def ZipFile.foreach(aZipFileName, &block)
897
1401
  ZipFile.open(aZipFileName) {
898
1402
  |zipFile|
@@ -900,10 +1404,16 @@ module Zip
900
1404
  }
901
1405
  end
902
1406
 
1407
+ # Returns an input stream to the specified entry. If a block is passed
1408
+ # the stream object is passed to the block and the stream is automatically
1409
+ # closed afterwards just as with ruby's builtin File.open method.
903
1410
  def get_input_stream(entry, &aProc)
904
1411
  get_entry(entry).get_input_stream(&aProc)
905
1412
  end
906
1413
 
1414
+ # Returns an output stream to the specified entry. If a block is passed
1415
+ # the stream object is passed to the block and the stream is automatically
1416
+ # closed afterwards just as with ruby's builtin File.open method.
907
1417
  def get_output_stream(entry, &aProc)
908
1418
  newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
909
1419
  if newEntry.directory?
@@ -915,50 +1425,53 @@ module Zip
915
1425
  zipStreamableEntry.get_output_stream(&aProc)
916
1426
  end
917
1427
 
1428
+ # Returns the name of the zip archive
918
1429
  def to_s
919
1430
  @name
920
1431
  end
921
1432
 
1433
+ # Returns a string containing the contents of the specified entry
922
1434
  def read(entry)
923
1435
  get_input_stream(entry) { |is| is.read }
924
1436
  end
925
1437
 
1438
+ # Convenience method for adding the contents of a file to the archive
926
1439
  def add(entry, srcPath, &continueOnExistsProc)
927
1440
  continueOnExistsProc ||= proc { false }
928
1441
  check_entry_exists(entry, continueOnExistsProc, "add")
929
1442
  newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s)
930
- if is_directory(newEntry, srcPath)
931
- @entrySet << ZipStreamableDirectory.new(newEntry)
932
- else
933
- @entrySet << ZipStreamableFile.new(newEntry, srcPath)
934
- end
1443
+ newEntry.gather_fileinfo_from_srcpath(srcPath)
1444
+ @entrySet << newEntry
935
1445
  end
936
1446
 
1447
+ # Removes the specified entry.
937
1448
  def remove(entry)
938
1449
  @entrySet.delete(get_entry(entry))
939
1450
  end
940
1451
 
1452
+ # Renames the specified entry.
941
1453
  def rename(entry, newName, &continueOnExistsProc)
942
1454
  foundEntry = get_entry(entry)
943
1455
  check_entry_exists(newName, continueOnExistsProc, "rename")
944
1456
  foundEntry.name=newName
945
1457
  end
946
1458
 
1459
+ # Replaces the specified entry with the contents of srcPath (from
1460
+ # the file system).
947
1461
  def replace(entry, srcPath)
948
1462
  check_file(srcPath)
949
1463
  add(remove(entry), srcPath)
950
1464
  end
951
-
1465
+
1466
+ # Extracts entry to file destPath.
952
1467
  def extract(entry, destPath, &onExistsProc)
953
1468
  onExistsProc ||= proc { false }
954
1469
  foundEntry = get_entry(entry)
955
- if foundEntry.is_directory
956
- create_directory(foundEntry, destPath, &onExistsProc)
957
- else
958
- write_file(foundEntry, destPath, &onExistsProc)
959
- end
1470
+ foundEntry.extract(destPath, &onExistsProc)
960
1471
  end
961
-
1472
+
1473
+ # Commits changes that has been made since the previous commit to
1474
+ # the zip archive.
962
1475
  def commit
963
1476
  return if ! commit_required?
964
1477
  on_success_replace(name) {
@@ -973,54 +1486,51 @@ module Zip
973
1486
  }
974
1487
  initialize(name)
975
1488
  end
976
-
1489
+
1490
+ # Closes the zip file committing any changes that has been made.
977
1491
  def close
978
1492
  commit
979
1493
  end
980
1494
 
1495
+ # Returns true if any changes has been made to this archive since
1496
+ # the previous commit
981
1497
  def commit_required?
982
1498
  return @entrySet != @storedEntries || @create == ZipFile::CREATE
983
1499
  end
984
1500
 
1501
+ # Searches for entry with the specified name. Returns nil if
1502
+ # no entry is found. See also get_entry
985
1503
  def find_entry(entry)
986
1504
  @entrySet.detect {
987
1505
  |e|
988
1506
  e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "")
989
1507
  }
990
1508
  end
991
-
1509
+
1510
+ # Searches for an entry just as find_entry, but throws Errno::ENOENT
1511
+ # if no entry is found.
992
1512
  def get_entry(entry)
993
1513
  selectedEntry = find_entry(entry)
994
1514
  unless selectedEntry
995
1515
  raise Errno::ENOENT, entry
996
1516
  end
1517
+ selectedEntry.restore_ownership = @restore_ownership
1518
+ selectedEntry.restore_permissions = @restore_permissions
1519
+ selectedEntry.restore_times = @restore_times
1520
+
997
1521
  return selectedEntry
998
1522
  end
999
1523
 
1000
- def mkdir(entryName, permissionInt = 0) #permissionInt ignored
1524
+ # Creates a directory
1525
+ def mkdir(entryName, permissionInt = 0755)
1001
1526
  if find_entry(entryName)
1002
1527
  raise Errno::EEXIST, "File exists - #{entryName}"
1003
1528
  end
1004
- @entrySet << ZipStreamableDirectory.new(ZipEntry.new(name, entryName.to_s.ensure_end("/")))
1529
+ @entrySet << ZipStreamableDirectory.new(@name, entryName.to_s.ensure_end("/"), nil, permissionInt)
1005
1530
  end
1006
1531
 
1007
1532
  private
1008
1533
 
1009
- def create_directory(entry, destPath)
1010
- if File.directory? destPath
1011
- return
1012
- elsif File.exists? destPath
1013
- if block_given? && yield(entry, destPath)
1014
- File.rm_f destPath
1015
- else
1016
- raise ZipDestinationFileExistsError,
1017
- "Cannot create directory '#{destPath}'. "+
1018
- "A file already exists with that name"
1019
- end
1020
- end
1021
- Dir.mkdir destPath
1022
- end
1023
-
1024
1534
  def is_directory(newEntry, srcPath)
1025
1535
  srcPathIsDirectory = File.directory?(srcPath)
1026
1536
  if newEntry.is_directory && ! srcPathIsDirectory
@@ -1045,18 +1555,6 @@ module Zip
1045
1555
  end
1046
1556
  end
1047
1557
 
1048
- def write_file(entry, destPath, continueOnExistsProc = proc { false })
1049
- if File.exists?(destPath) && ! yield(entry, destPath)
1050
-
1051
- # raise ZipDestinationFileExistsError,
1052
- $stderr.puts "Destination '#{destPath}' already exists"
1053
- end
1054
- File.open(destPath, "wb") {
1055
- |os|
1056
- entry.get_input_stream { |is| os << is.read }
1057
- }
1058
- end
1059
-
1060
1558
  def check_file(path)
1061
1559
  unless File.readable? path
1062
1560
  raise Errno::ENOENT, path
@@ -1080,43 +1578,13 @@ module Zip
1080
1578
 
1081
1579
  end
1082
1580
 
1083
- class ZipStreamableFile < DelegateClass(ZipEntry) #:nodoc:all
1084
- def initialize(entry, filepath)
1085
- super(entry)
1086
- @delegate = entry
1087
- @filepath = filepath
1088
- end
1581
+ class ZipStreamableDirectory < ZipEntry
1582
+ def initialize(zipfile, entry, srcPath = nil, permissionInt = nil)
1583
+ super(zipfile, entry)
1089
1584
 
1090
- def get_input_stream(&aProc)
1091
- File.open(@filepath, "rb", &aProc)
1092
- end
1093
-
1094
- def write_to_zip_output_stream(aZipOutputStream)
1095
- aZipOutputStream.put_next_entry(self)
1096
- aZipOutputStream << get_input_stream { |is| is.read }
1097
- end
1098
-
1099
- def == (other)
1100
- return false unless other.class == ZipStreamableFile
1101
- @filepath == other.filepath && super(other.delegate)
1102
- end
1103
-
1104
- protected
1105
- attr_reader :filepath, :delegate
1106
- end
1107
-
1108
- class ZipStreamableDirectory < DelegateClass(ZipEntry) #:nodoc:all
1109
- def initialize(entry)
1110
- super(entry)
1111
- end
1112
-
1113
- def get_input_stream(&aProc)
1114
- return yield(NullInputStream.instance) if block_given?
1115
- NullInputStream.instance
1116
- end
1117
-
1118
- def write_to_zip_output_stream(aZipOutputStream)
1119
- aZipOutputStream.put_next_entry(self)
1585
+ @ftype = :directory
1586
+ entry.get_extra_attributes_from_path(srcPath) if (srcPath)
1587
+ @unix_perms = permissionInt if (permissionInt)
1120
1588
  end
1121
1589
  end
1122
1590
 
@@ -1144,6 +1612,7 @@ module Zip
1144
1612
  raise StandardError, "cannot open entry for reading while its open for writing - #{name}"
1145
1613
  end
1146
1614
  @tempFile.open # reopens tempfile from top
1615
+ @tempFile.binmode
1147
1616
  if block_given?
1148
1617
  begin
1149
1618
  yield(@tempFile)
@@ -1157,7 +1626,7 @@ module Zip
1157
1626
 
1158
1627
  def write_to_zip_output_stream(aZipOutputStream)
1159
1628
  aZipOutputStream.put_next_entry(self)
1160
- aZipOutputStream << get_input_stream { |is| is.read }
1629
+ get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) }
1161
1630
  end
1162
1631
  end
1163
1632
 
@@ -1258,8 +1727,8 @@ module Zip
1258
1727
  register_map
1259
1728
 
1260
1729
  def initialize(binstr = nil)
1261
- @uid = nil
1262
- @gid = nil
1730
+ @uid = 0
1731
+ @gid = 0
1263
1732
  binstr and merge(binstr)
1264
1733
  end
1265
1734
  attr_accessor :uid, :gid