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.
- data/code/01rwdcore/01rwdcore.rb +6 -6
- data/code/01rwdcore/02helptexthashbegin.rb +13 -11
- data/code/01rwdcore/jumplinkcommand.rb +14 -4
- data/code/01rwdcore/openhelpwindow.rb +7 -0
- data/code/01rwdcore/runhelpabout.rb +6 -1
- data/code/01rwdcore/runtab.rb +15 -0
- data/code/01rwdcore/selectiontab.rb +2 -0
- data/code/01rwdcore/setuphelpaboutoptions.rb +2 -0
- data/code/01rwdcore/setuptinkerdocuments.rb +1 -0
- data/code/01rwdcore/test_cases.rb +100 -51
- data/code/01rwdcore/test_harness.rb +8 -1
- data/code/01rwdcore/uploadreturns.rb +3 -0
- data/code/dd0viewphoto/dd0viewphoto.rb +2 -0
- data/code/superant.com.rwdcalendar/gh9calendar.rb +40 -0
- data/code/superant.com.rwdcalendar/helptexthashrwdschedule.rb +14 -0
- data/code/superant.com.rwdcalendar/openhelpwindowrwdschedule.rb +17 -0
- data/code/superant.com.rwdtinkerbackwindow/changelocale.rb +84 -0
- data/code/superant.com.rwdtinkerbackwindow/initiateapplets.rb +0 -1
- data/code/superant.com.rwdtinkerbackwindow/installgemapplet.rb +2 -0
- data/code/superant.com.rwdtinkerbackwindow/listgemzips.rb +2 -0
- data/code/superant.com.rwdtinkerbackwindow/listinstalledfiles.rb +3 -1
- data/code/superant.com.rwdtinkerbackwindow/listzips.rb +4 -0
- data/code/superant.com.rwdtinkerbackwindow/loadconfigurationrecord.rb +3 -1
- data/code/superant.com.rwdtinkerbackwindow/openhelpwindowtinkerwin2.rb +3 -1
- data/code/superant.com.rwdtinkerbackwindow/showlocaleoptions.rb +9 -0
- data/code/superant.com.rwdtinkerbackwindow/viewappletcontents.rb +1 -0
- data/code/superant.com.rwdtinkerbackwindow/viewlogfile.rb +8 -5
- data/code/superant.com.schedule/0uninstallapplet.rb +24 -0
- data/code/superant.com.schedule/archiveevent.rb +14 -0
- data/code/superant.com.schedule/archiveicsevent.rb +14 -0
- data/code/superant.com.schedule/cleareventscreendisplay.rb +19 -0
- data/code/superant.com.schedule/deleteeventrecord.rb +19 -0
- data/code/superant.com.schedule/deleteicseventrecord.rb +19 -0
- data/code/superant.com.schedule/deleterwdscheduleupdatefiles.rb +20 -0
- data/code/superant.com.schedule/downloadrwdschedulefiles.rb +37 -0
- data/code/superant.com.schedule/exporticseventrecord.rb +97 -0
- data/code/{superant.com.foldeditor → superant.com.schedule}/helptexthashload.rb +2 -2
- data/code/superant.com.schedule/listeventdates.rb +19 -0
- data/code/superant.com.schedule/listicseventdates.rb +19 -0
- data/code/{superant.com.foldeditor → superant.com.schedule}/loadconfigurationrecord.rb +4 -4
- data/code/superant.com.schedule/loadconfigurationvariables.rb +14 -0
- data/code/superant.com.schedule/loadeventrecord.rb +38 -0
- data/code/superant.com.schedule/loadicseventrecord.rb +30 -0
- data/code/superant.com.schedule/openhelpwindowrwdschedule.rb +43 -0
- data/code/superant.com.schedule/renameeventdata.rb +14 -0
- data/code/superant.com.schedule/renameicseventdata.rb +17 -0
- data/code/{superant.com.foldeditor/runrwdapplet.rb → superant.com.schedule/returntomain.rb} +3 -3
- data/code/superant.com.schedule/runrwdscheduleicsbackwindow.rb +10 -0
- data/code/superant.com.schedule/runrwdschedulemenu1.rb +34 -0
- data/code/superant.com.schedule/runrwdschedulesyncbackwindow.rb +10 -0
- data/code/{superant.com.foldeditor/rwdtinkerversion.rb → superant.com.schedule/rwdversion.rb} +2 -2
- data/code/{superant.com.foldeditor → superant.com.schedule}/saveconfigurationrecord.rb +5 -6
- data/code/superant.com.schedule/saveeventrecord.rb +25 -0
- data/code/superant.com.schedule/saveicseventrecord.rb +98 -0
- data/code/superant.com.schedule/syncrwdschedule.rb +30 -0
- data/code/superant.com.schedule/test_cases.rb +45 -0
- data/code/superant.com.schedule/uploadrwdschedulefiles.rb +30 -0
- data/code/superant.com.schedule/viewevent.rb +20 -0
- data/code/superant.com.schedule/viewicsevent.rb +20 -0
- data/code/superant.com.schedule/viewrwdschedulesconfiguration.rb +21 -0
- data/code/zz0applicationend/zz0end.rb +2 -1
- data/configuration/bigtinker.dist +5 -2
- data/configuration/rwdtinker.dist +3 -3
- data/configuration/rwdwschedule.dist +28 -0
- data/configuration/tinkerwin2variables.dist +1 -1
- data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/1appname.rwd +5 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/20downloadftp.rwd +45 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/67viewconfiguration.rwd +29 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/70rwddiagnostics.rwd +16 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedulebackwindow/m01menubegin.rwd +18 -0
- data/gui/tinkerbackwindows/{superant.com.foldeditor/9end.rwd → superant.com.rwdschedulebackwindow/zvbackend.rwd} +6 -6
- data/gui/tinkerbackwindows/superant.com.rwdschedules/1appname.rwd +5 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedules/gg0viewevent.rwd +27 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedules/gl6editrecord.rwd +56 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedules/gl8contactutilities.rwd +25 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedules/hl9calendar.rwd +27 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedules/m01menubegin.rwd +18 -0
- data/gui/tinkerbackwindows/{superant.com.slideshow/9end.rwd → superant.com.rwdschedules/zvbackend.rwd} +6 -6
- data/gui/tinkerbackwindows/superant.com.rwdschedulesback/1appname.rwd +5 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedulesback/30viewevent.rwd +27 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedulesback/40editrecord.rwd +49 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedulesback/60eventicsutilities.rwd +25 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedulesback/m01menubegin.rwd +18 -0
- data/gui/tinkerbackwindows/superant.com.rwdschedulesback/zvbackend.rwd +6 -0
- data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/70rwddiagnostics.rwd +1 -1
- data/gui/tinkerbackwindows/superant.com.tinkerbackwindow/80localechanger.rwd +17 -0
- data/init.rb +7 -1
- data/installed/rwdscheduledate2.inf +6 -0
- data/installed/rwdwschedule.inf +20 -0
- data/lang/en/rwdcalendar/en.po +32 -0
- data/lang/en/rwdcore/en.po +31 -0
- data/lang/es/rwdcalendar/es.po +10 -0
- data/lang/es/rwdcore/es.po +27 -15
- data/lang/fr/rwdcalendar/fr.po +11 -0
- data/lang/fr/rwdcore/fr.po +4 -1
- data/lang/hi/rwdcalendar/hi.po +11 -0
- data/lang/hi/rwdcore/hi.po +39 -36
- data/lang/ja/rwdcalendar/ja.po +11 -0
- data/lang/ja/rwdcore/ja.po +3 -0
- data/lang/nl/rwdcalendar/nl.po +12 -0
- data/lang/nl/rwdcore/nl.po +4 -1
- data/lib/cal.rb +158 -0
- data/lib/icalendar.rb +18 -0
- data/lib/icalendar/base.rb +17 -0
- data/lib/icalendar/calendar.rb +44 -0
- data/lib/icalendar/calendar_parser.rb +237 -0
- data/lib/icalendar/component.rb +91 -0
- data/lib/icalendar/component/alarm.rb +16 -0
- data/lib/icalendar/component/event.rb +25 -0
- data/lib/icalendar/component/freebusy.rb +12 -0
- data/lib/icalendar/component/journal.rb +25 -0
- data/lib/icalendar/component/timezone.rb +26 -0
- data/lib/icalendar/component/todo.rb +21 -0
- data/lib/icalendar/helpers.rb +103 -0
- data/lib/icalendar/parameter.rb +25 -0
- data/lib/zip/ioextras.rb +43 -2
- data/lib/zip/stdrubyext.rb +5 -5
- data/lib/zip/tempfile_bugfixed.rb +2 -2
- data/lib/zip/zip.rb +618 -149
- data/lib/zip/zipfilesystem.rb +59 -8
- data/lib/zip/ziprequire.rb +32 -3
- data/rwd_files/HowTo_BigTinker.txt +15 -3
- data/rwd_files/HowTo_Schedule.txt +265 -0
- data/rwd_files/HowTo_Tinker.txt +14 -0
- data/rwd_files/Tinkerhelptexthash.txt +5 -2
- data/rwd_files/rwdapplications.html +23 -1
- data/rwd_files/rwdschedulehelpfiles.txt +19 -0
- data/rwd_files/schedules/20050120T09.ics +9 -0
- data/rwd_files/schedules/200505may02a.sch +4 -0
- data/rwd_files/schedules/Enterprise.ics +411 -0
- data/rwd_files/schedules/US Holidays.ics +575 -0
- data/rwd_files/schedules/archive/sample.archive +1 -0
- data/rwd_files/schedules/testics05.ics +11 -0
- data/rwdconfig.dist +4 -2
- data/{bigtinker.rb → rwdtinker.rb} +0 -0
- data/tests/{makedist-rwdwfoldeditor.rb → makedist-rwdwhypernote.rb} +4 -8
- data/tests/makedist.rb +1 -1
- data/updates/temp.rb +1 -0
- data/zips/rwdwfoldeditor-0.07.zip +0 -0
- data/zips/rwdwhypernote-0.16.zip +0 -0
- data/zips/rwdwschedule-1.07.zip +0 -0
- data/zips/tinkerbellw-0.04.zip +0 -0
- metadata +102 -54
- data/code/superant.com.foldeditor/0uninstallapplet.rb +0 -17
- data/code/superant.com.foldeditor/changehypernotename.rb +0 -21
- data/code/superant.com.foldeditor/chooselinkfile.rb +0 -6
- data/code/superant.com.foldeditor/choosenotefile.rb +0 -6
- data/code/superant.com.foldeditor/clearnotescreen.rb +0 -7
- data/code/superant.com.foldeditor/createnewnotehtml.rb +0 -31
- data/code/superant.com.foldeditor/hyperlinkcreatelinkfile.rb +0 -19
- data/code/superant.com.foldeditor/launchfoldeditorurl.rb +0 -19
- data/code/superant.com.foldeditor/listfoldeditorfiles.rb +0 -7
- data/code/superant.com.foldeditor/loadconfigurationvariables.rb +0 -14
- data/code/superant.com.foldeditor/loadfolddocument.rb +0 -18
- data/code/superant.com.foldeditor/loadnextnote.rb +0 -32
- data/code/superant.com.foldeditor/loadprevnote.rb +0 -32
- data/code/superant.com.foldeditor/loadrwdfoldeditlinkfile.rb +0 -19
- data/code/superant.com.foldeditor/openhelpwindowrwdhyernote.rb +0 -30
- data/code/superant.com.foldeditor/rwddisplayfoldlinks.rb +0 -41
- data/code/superant.com.foldeditor/savefoldlinkfile.rb +0 -20
- data/code/superant.com.foldeditor/savehtmlhypernoterecord.rb +0 -20
- data/configuration/rwdwfoldeditor.dist +0 -16
- data/gui/tinkerbackwindows/superant.com.foldeditor/10appletbegin.rwd +0 -4
- data/gui/tinkerbackwindows/superant.com.foldeditor/10aviewnote.rwd +0 -36
- data/gui/tinkerbackwindows/superant.com.foldeditor/15htmlview.rwd +0 -43
- data/gui/tinkerbackwindows/superant.com.foldeditor/56viewfold.rwd +0 -43
- data/gui/tinkerbackwindows/superant.com.foldeditor/67viewconfiguration.rwd +0 -27
- data/gui/tinkerbackwindows/superant.com.foldeditor/81jumplinkcommands.rwd +0 -17
- data/gui/tinkerbackwindows/superant.com.slideshow/10appletbegin.rwd +0 -4
- data/gui/tinkerbackwindows/superant.com.slideshow/11viewnamedata.rwd +0 -19
- data/gui/tinkerbackwindows/superant.com.slideshow/13listnamerecordfiles.rwd +0 -21
- data/gui/tinkerbackwindows/superant.com.slideshow/16editrecord.rwd +0 -39
- data/gui/tinkerbackwindows/superant.com.slideshow/17viewvcardrecord.rwd +0 -32
- data/gui/tinkerbackwindows/superant.com.slideshow/18contactutilities.rwd +0 -34
- data/gui/tinkerbackwindows/superant.com.slideshow/81jumplinkcommands.rwd +0 -17
- data/installed/rwdwfoldeditor.inf +0 -11
- data/rwd_files/HowTo_FoldEditor.txt +0 -131
- data/rwd_files/default.fld +0 -9
- data/rwd_files/rubylinks.fld +0 -6
- data/rwd_files/rwdfoldeditorhelpfiles.txt +0 -42
- data/rwd_files/rwdhypernote-0.13.fld +0 -202
- data/zips/rwdwfoldeditor-0.06.zip +0 -0
- data/zips/rwdwschedule-1.05.zip +0 -0
- 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
|
-
#
|
|
109
|
+
# Implements many of the output convenience methods of IO.
|
|
110
|
+
# relies on <<
|
|
70
111
|
module AbstractOutputStream
|
|
71
112
|
include FakeIO
|
|
72
113
|
|
data/lib/zip/stdrubyext.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
502
|
+
@header_signature ,
|
|
311
503
|
@version ,
|
|
312
504
|
@fstype ,
|
|
313
|
-
@
|
|
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 (
|
|
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 , # @
|
|
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
|
-
|
|
569
|
+
|
|
570
|
+
@header_signature ,
|
|
379
571
|
@version , # version of encoding software
|
|
380
572
|
@fstype , # filesystem type
|
|
381
573
|
@versionNeededToExtract,
|
|
382
|
-
@
|
|
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 (
|
|
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 , # @
|
|
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 ==
|
|
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
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
931
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
1084
|
-
def initialize(entry,
|
|
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
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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
|
-
|
|
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 =
|
|
1262
|
-
@gid =
|
|
1730
|
+
@uid = 0
|
|
1731
|
+
@gid = 0
|
|
1263
1732
|
binstr and merge(binstr)
|
|
1264
1733
|
end
|
|
1265
1734
|
attr_accessor :uid, :gid
|