mhc 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/COPYRIGHT +28 -0
- data/Gemfile +8 -0
- data/README.org +209 -0
- data/Rakefile +13 -0
- data/bin/mhc +312 -0
- data/emacs/Cask +25 -0
- data/emacs/Makefile +58 -0
- data/emacs/mhc-calendar.el +1723 -0
- data/emacs/mhc-calfw.el +135 -0
- data/emacs/mhc-compat.el +90 -0
- data/emacs/mhc-date.el +642 -0
- data/emacs/mhc-day.el +149 -0
- data/emacs/mhc-db.el +158 -0
- data/emacs/mhc-draft.el +211 -0
- data/emacs/mhc-e21.el +167 -0
- data/emacs/mhc-face.el +236 -0
- data/emacs/mhc-file.el +224 -0
- data/emacs/mhc-guess.el +648 -0
- data/emacs/mhc-header.el +176 -0
- data/emacs/mhc-logic.el +563 -0
- data/emacs/mhc-message.el +130 -0
- data/emacs/mhc-minibuf.el +466 -0
- data/emacs/mhc-misc.el +248 -0
- data/emacs/mhc-mua.el +260 -0
- data/emacs/mhc-parse.el +286 -0
- data/emacs/mhc-process.el +35 -0
- data/emacs/mhc-ps.el +1174 -0
- data/emacs/mhc-record.el +201 -0
- data/emacs/mhc-schedule.el +202 -0
- data/emacs/mhc-summary.el +763 -0
- data/emacs/mhc-sync.el +158 -0
- data/emacs/mhc-vars.el +149 -0
- data/emacs/mhc.el +1114 -0
- data/icons/Anniversary.xbm +6 -0
- data/icons/Anniversary.xpm +27 -0
- data/icons/Birthday.xbm +6 -0
- data/icons/Birthday.xpm +25 -0
- data/icons/Business.xbm +6 -0
- data/icons/Business.xpm +24 -0
- data/icons/CheckBox.xbm +6 -0
- data/icons/CheckBox.xpm +24 -0
- data/icons/CheckedBox.xbm +6 -0
- data/icons/CheckedBox.xpm +25 -0
- data/icons/Conflict.xbm +6 -0
- data/icons/Conflict.xpm +22 -0
- data/icons/Date.xbm +6 -0
- data/icons/Date.xpm +29 -0
- data/icons/Holiday.xbm +6 -0
- data/icons/Holiday.xpm +25 -0
- data/icons/Link.xbm +6 -0
- data/icons/Link.xpm +25 -0
- data/icons/Other.xbm +6 -0
- data/icons/Other.xpm +28 -0
- data/icons/Party.xbm +6 -0
- data/icons/Party.xpm +23 -0
- data/icons/Private.xbm +6 -0
- data/icons/Private.xpm +26 -0
- data/icons/Recurrence.xbm +6 -0
- data/icons/Recurrence.xpm +98 -0
- data/icons/Vacation.xbm +6 -0
- data/icons/Vacation.xpm +26 -0
- data/lib/mhc.rb +45 -0
- data/lib/mhc/builder.rb +64 -0
- data/lib/mhc/caldav.rb +304 -0
- data/lib/mhc/calendar.rb +106 -0
- data/lib/mhc/command.rb +13 -0
- data/lib/mhc/command/cache.rb +14 -0
- data/lib/mhc/command/completions.rb +108 -0
- data/lib/mhc/command/init.rb +133 -0
- data/lib/mhc/command/scan.rb +33 -0
- data/lib/mhc/command/sync.rb +22 -0
- data/lib/mhc/config.rb +229 -0
- data/lib/mhc/converter.rb +330 -0
- data/lib/mhc/datastore.rb +164 -0
- data/lib/mhc/date_enumerator.rb +274 -0
- data/lib/mhc/date_frame.rb +124 -0
- data/lib/mhc/date_helper.rb +49 -0
- data/lib/mhc/etag.rb +68 -0
- data/lib/mhc/event.rb +396 -0
- data/lib/mhc/formatter.rb +312 -0
- data/lib/mhc/logger.rb +94 -0
- data/lib/mhc/modifier.rb +149 -0
- data/lib/mhc/occurrence.rb +94 -0
- data/lib/mhc/occurrence_enumerator.rb +113 -0
- data/lib/mhc/property_value.rb +33 -0
- data/lib/mhc/property_value/date.rb +190 -0
- data/lib/mhc/property_value/integer.rb +15 -0
- data/lib/mhc/property_value/list.rb +41 -0
- data/lib/mhc/property_value/period.rb +49 -0
- data/lib/mhc/property_value/range.rb +100 -0
- data/lib/mhc/property_value/recurrence_condition.rb +272 -0
- data/lib/mhc/property_value/text.rb +11 -0
- data/lib/mhc/property_value/time.rb +45 -0
- data/lib/mhc/query.rb +210 -0
- data/lib/mhc/sync.rb +46 -0
- data/lib/mhc/sync/driver.rb +108 -0
- data/lib/mhc/sync/status.rb +70 -0
- data/lib/mhc/sync/status_manager.rb +142 -0
- data/lib/mhc/sync/strategy.rb +233 -0
- data/lib/mhc/sync/syncinfo.rb +98 -0
- data/lib/mhc/templates/config.yml.erb +142 -0
- data/lib/mhc/version.rb +4 -0
- data/lib/mhc/webdav.rb +319 -0
- data/mhc.gemspec +24 -0
- data/samples/DOT.mhc-config.yml +116 -0
- data/samples/japanese-holidays.mhcc +153 -0
- data/samples/mhc-completions.zsh +11 -0
- data/spec/mhc_spec.rb +682 -0
- data/spec/spec_helper.rb +9 -0
- data/xpm/close.xpm +18 -0
- data/xpm/delete.xpm +19 -0
- data/xpm/exit.xpm +18 -0
- data/xpm/month.xpm +18 -0
- data/xpm/next.xpm +18 -0
- data/xpm/next2.xpm +18 -0
- data/xpm/next_year.xpm +18 -0
- data/xpm/open.xpm +19 -0
- data/xpm/prev.xpm +18 -0
- data/xpm/prev2.xpm +18 -0
- data/xpm/prev_year.xpm +18 -0
- data/xpm/save.xpm +19 -0
- data/xpm/today.xpm +18 -0
- metadata +214 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
module Mhc
|
2
|
+
module Sync
|
3
|
+
##
|
4
|
+
# It wraps existing database to adds ability to manage etag cache for sync status tracking.
|
5
|
+
# The downstream database is supposed to respond to:
|
6
|
+
#
|
7
|
+
# 1. report_etags
|
8
|
+
# report_etags(uids = nil) # returns one of:
|
9
|
+
# => {uid_string => etag_object } # Hash
|
10
|
+
# => {uid_string => etag_string } # Hash
|
11
|
+
# => [uid_etag_object] # Array
|
12
|
+
#
|
13
|
+
# uid_etag_object is an object which respond to #etag and #uid method.
|
14
|
+
# etag_object is an object which respond to #etag method.
|
15
|
+
#
|
16
|
+
# 2. get_with_etag
|
17
|
+
# get_with_etag(uid)
|
18
|
+
# # => [RECORD, etag]
|
19
|
+
#
|
20
|
+
# Each RECORD has to respond to #to_ics_string or #body that
|
21
|
+
# returns iCalendar-conformed string.
|
22
|
+
#
|
23
|
+
# 3. put_if_match
|
24
|
+
# put_if_match(uid, ics_string, expected_etag)
|
25
|
+
# # => put ics_string if "ETAG" equals to expected_etag
|
26
|
+
#
|
27
|
+
# 4. delete_if_match
|
28
|
+
# delete_if_match(uid, expected_etag)
|
29
|
+
# # => delete uid if "ETAG" equals to expected_etag
|
30
|
+
#
|
31
|
+
# expected_etag is a string for sync. if expected_etag is omitted
|
32
|
+
# (or nil), put_if_match and delete_if_match will ignore
|
33
|
+
# conflictions.
|
34
|
+
#
|
35
|
+
class StatusManager
|
36
|
+
def initialize(real_db, etag_db)
|
37
|
+
@db, @etag_db = real_db, etag_db
|
38
|
+
refresh_status
|
39
|
+
end
|
40
|
+
|
41
|
+
def syncinfo(uid)
|
42
|
+
Status.new(uid, self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def uid_list
|
46
|
+
(@db_status.keys + @etag_status.keys).sort.uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# delegation to original DB with sync-status check.
|
51
|
+
#
|
52
|
+
# To make sure any kind of update is safe,
|
53
|
+
# you may want to check current_record.ex_etag == current_record.etag
|
54
|
+
# (means current_record.unmodified? is true) like:
|
55
|
+
#
|
56
|
+
# if current_record.unmodified?
|
57
|
+
# @db.put(new_record)
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# However, this will not work. Because this check-and-update
|
61
|
+
# must be an atomic operation.
|
62
|
+
# So, instead, we have to do:
|
63
|
+
#
|
64
|
+
# @db.put_if_match(new_record, current_record.ex_etag)
|
65
|
+
#
|
66
|
+
def get(uid)
|
67
|
+
res, etag = @db.get_with_etag(uid)
|
68
|
+
@db_status[uid] = etag if etag
|
69
|
+
return Status.new(uid, self, res)
|
70
|
+
end
|
71
|
+
|
72
|
+
def put(modified_record, overwrite = false)
|
73
|
+
current_record = syncinfo(modified_record.uid)
|
74
|
+
expected_etag = overwrite ? nil : current_record.ex_etag
|
75
|
+
|
76
|
+
if @db.put_if_match(current_record.uid, modified_record.to_ics_string, expected_etag)
|
77
|
+
## XXX: put_if_match should return the new etag value, and we
|
78
|
+
## have to use it as a new etag for the current record.
|
79
|
+
## However, some CalDAV servers (Google Calendar) do not
|
80
|
+
## return any etag on PUT.
|
81
|
+
## So, we have to PROPFIND immediately after the
|
82
|
+
## PUT. This is a small crack on atomicity
|
83
|
+
refresh_status(current_record.uid) # refresh propfind cache.
|
84
|
+
current_record.mark_synced
|
85
|
+
return Status.new(current_record.uid, self)
|
86
|
+
else
|
87
|
+
return nil # put failed.
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def delete(uid)
|
92
|
+
current_record = syncinfo(uid)
|
93
|
+
if @db.delete_if_match(current_record.uid, current_record.ex_etag)
|
94
|
+
refresh_status(current_record.uid)
|
95
|
+
current_record.mark_synced
|
96
|
+
return Status.new(current_record.uid, self)
|
97
|
+
else
|
98
|
+
return nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# propfind with cache
|
103
|
+
def refresh_status(uids = :all)
|
104
|
+
### XXX: care fore UIDs for partial update.
|
105
|
+
@db_status = make_hash(@db.report_etags)
|
106
|
+
@etag_status = make_hash(@etag_db.report_etags)
|
107
|
+
end
|
108
|
+
|
109
|
+
def etag(uid)
|
110
|
+
if @db_status[uid].respond_to?(:etag)
|
111
|
+
return @db_status[uid].etag
|
112
|
+
else
|
113
|
+
return @db_status[uid]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def ex_etag(uid)
|
118
|
+
@etag_status[uid]
|
119
|
+
end
|
120
|
+
|
121
|
+
def mark_synced(uid, etag)
|
122
|
+
@etag_db.put(uid, etag)
|
123
|
+
return self
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
## etag_report is one of:
|
128
|
+
## + {uid_string => etag_object } style hash,
|
129
|
+
## + {uid_string => etag_string } style hash,
|
130
|
+
## + [uid_etag_object] style array,
|
131
|
+
## uid_etag_object is an object which respond to #etag and #uid method.
|
132
|
+
## etag_object is an object which respond to #etag method.
|
133
|
+
def make_hash(etag_report)
|
134
|
+
return etag_report if etag_report.respond_to?(:keys)
|
135
|
+
hash = {}
|
136
|
+
etag_report.map {|o| hash[o.uid] = o.etag} if etag_report
|
137
|
+
return hash
|
138
|
+
end
|
139
|
+
|
140
|
+
end # class StatusManager
|
141
|
+
end # module Sync
|
142
|
+
end # module Mhc
|
@@ -0,0 +1,233 @@
|
|
1
|
+
module Mhc
|
2
|
+
module Sync
|
3
|
+
module Strategy
|
4
|
+
|
5
|
+
class Factory
|
6
|
+
def self.create(strategy)
|
7
|
+
case strategy.to_sym
|
8
|
+
when :mirror
|
9
|
+
return Mirror.new
|
10
|
+
when :sync
|
11
|
+
return Sync.new
|
12
|
+
else
|
13
|
+
raise NotImplementedError, "#{strategy} #{strategy.class}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Our Sync mechanism is very simple, because
|
19
|
+
# we can assume every article is independent
|
20
|
+
# with eath other. It will work well with
|
21
|
+
# iCalendar basis articles.
|
22
|
+
#
|
23
|
+
# We simply follow the rule on the table below:
|
24
|
+
#
|
25
|
+
# Side 2
|
26
|
+
# |---+---------+------------+------------+-------|
|
27
|
+
# S | | M | U | N | D |
|
28
|
+
# i |---+---------+------------+------------+-------|
|
29
|
+
# d | M | CNF | OW 1->2 | CP 1->2 | CNF |
|
30
|
+
# e | U | OW 2->1 | - | ?? CP 1->2 | DEL 1 |
|
31
|
+
# 1 | N | CP 2->1 | ?? CP 2->1 | - | - |
|
32
|
+
# | D | CNF | DEL 2 | - | - |
|
33
|
+
# |---+---------+------------+------------+-------|
|
34
|
+
#
|
35
|
+
# M, U, N, and D indicate status changes on each article after
|
36
|
+
# the last sync:
|
37
|
+
#
|
38
|
+
# + M :: Modified (or Created)
|
39
|
+
# + U :: Unchanged
|
40
|
+
# + N :: No Record
|
41
|
+
# + D :: Deleted
|
42
|
+
#
|
43
|
+
# Each entry in the table means:
|
44
|
+
# + -- :: No operation (ignore)
|
45
|
+
# + ?? :: Not occurred in normal cases
|
46
|
+
# + OW :: Overwrite
|
47
|
+
# + CP :: Copy
|
48
|
+
# + DEL :: Delete
|
49
|
+
# + CNF :: Conflict
|
50
|
+
#
|
51
|
+
# Before applying the rule to our repository,
|
52
|
+
# we have to set the marks (M, U, N or D) to all articles
|
53
|
+
# in each side.
|
54
|
+
#
|
55
|
+
# strategy = Mhc::Sync::Strategy.create(strategy_name)
|
56
|
+
# strategy name is one of:
|
57
|
+
# * :empty ... ignore on every status
|
58
|
+
# * :mirror ... mirror from side1 to side2
|
59
|
+
# * :sync ... sync articles of side1 and side2
|
60
|
+
#
|
61
|
+
# and strategy.whatnow(side1, side2) returns a symbol one of:
|
62
|
+
# * :ignore :: Already synced, ignoreable
|
63
|
+
# * :conflict :: Conflicted
|
64
|
+
# * :delete1 :: Should delete the article of side1
|
65
|
+
# * :delete2 :: Should delete the article of side2
|
66
|
+
# * :copy1_to_2 :: Should copy the article of side1 to side2
|
67
|
+
# * :copy2_to_1 :: Should copy the article of side2 to side1
|
68
|
+
# * :overwrite1_to_2 :: Should overwrite the article of side1 to side2
|
69
|
+
# * :overwrite2_to_1 :: Should overwrite the article of side2 to side1
|
70
|
+
#
|
71
|
+
# side1 and side2 have to respond to:
|
72
|
+
# #nil?, # #modified?, #unmodified?, #norecord?, #deleted?
|
73
|
+
#
|
74
|
+
class Base
|
75
|
+
def whatnow(side1, side2)
|
76
|
+
# do nothing
|
77
|
+
actions = {
|
78
|
+
"MM" => :ignore,
|
79
|
+
"MU" => :ignore,
|
80
|
+
"MN" => :ignore,
|
81
|
+
"MD" => :ignore,
|
82
|
+
|
83
|
+
"UM" => :ignore,
|
84
|
+
"UU" => :ignore,
|
85
|
+
"UN" => :ignore,
|
86
|
+
"UD" => :ignore,
|
87
|
+
|
88
|
+
"NM" => :ignore,
|
89
|
+
"NU" => :ignore,
|
90
|
+
"NN" => :ignore,
|
91
|
+
"ND" => :ignore,
|
92
|
+
|
93
|
+
"DM" => :ignore,
|
94
|
+
"DU" => :ignore,
|
95
|
+
"DN" => :ignore,
|
96
|
+
"DD" => :ignore,
|
97
|
+
}
|
98
|
+
return actions[status_pair(side1, side2)]
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
# * Char (M,U,N,D) indicates status change on each article
|
103
|
+
# after the last sync:
|
104
|
+
#
|
105
|
+
# + M :: Modified
|
106
|
+
# + U :: Unchanged
|
107
|
+
# + N :: No Record
|
108
|
+
# + D :: Deleted
|
109
|
+
#
|
110
|
+
def status_signature(info)
|
111
|
+
return "N" if info.nil?
|
112
|
+
|
113
|
+
return "M" if info.modified?
|
114
|
+
return "U" if info.unmodified?
|
115
|
+
return "N" if info.norecord?
|
116
|
+
return "D" if info.deleted?
|
117
|
+
|
118
|
+
return "?" # NOTREACHED I hope
|
119
|
+
end
|
120
|
+
|
121
|
+
def status_pair(side1, side2)
|
122
|
+
return status_signature(side1) + status_signature(side2)
|
123
|
+
end
|
124
|
+
end # class Base
|
125
|
+
|
126
|
+
# * Sync side1 and side2
|
127
|
+
#
|
128
|
+
# simply follow the rule on the table below:
|
129
|
+
#
|
130
|
+
# Side 2
|
131
|
+
# |---+---------+------------+------------+-------|
|
132
|
+
# S | | M | U | N | D |
|
133
|
+
# i |---+---------+------------+------------+-------|
|
134
|
+
# d | M | CNF | CP 1->2 | CP 1->2 | CNF |
|
135
|
+
# e | U | CP 2->1 | - | ?? - | DEL 1 |
|
136
|
+
# 1 | N | CP 2->1 | ?? - | - | - |
|
137
|
+
# | D | CNF | DEL 2 | - | - |
|
138
|
+
# |---+---------+------------+------------+-------|
|
139
|
+
#
|
140
|
+
# + M :: Modified (or Created)
|
141
|
+
# + U :: Unchanged
|
142
|
+
# + N :: No Record
|
143
|
+
# + D :: Deleted
|
144
|
+
#
|
145
|
+
# + -- :: No operation (ignore)
|
146
|
+
# + ?? :: Not occurred in normal cases
|
147
|
+
# + OW :: Overwrite
|
148
|
+
# + CP :: Copy
|
149
|
+
# + DEL :: Delete
|
150
|
+
# + CNF :: Conflict
|
151
|
+
#
|
152
|
+
class Sync < Base
|
153
|
+
def whatnow(side1, side2)
|
154
|
+
actions = {
|
155
|
+
"MM" => :conflict,
|
156
|
+
"MU" => :copy1_to_2,
|
157
|
+
"MN" => :copy1_to_2,
|
158
|
+
"MD" => :conflict,
|
159
|
+
|
160
|
+
"UM" => :copy2_to_1,
|
161
|
+
"UU" => :ignore,
|
162
|
+
"UN" => :ignore,
|
163
|
+
"UD" => :delete1,
|
164
|
+
|
165
|
+
"NM" => :copy2_to_1,
|
166
|
+
"NU" => :ignore,
|
167
|
+
"NN" => :ignore,
|
168
|
+
"ND" => :ignore,
|
169
|
+
|
170
|
+
"DM" => :conflict,
|
171
|
+
"DU" => :delete2,
|
172
|
+
"DN" => :ignore,
|
173
|
+
"DD" => :ignore,
|
174
|
+
}
|
175
|
+
return actions[status_pair(side1, side2)]
|
176
|
+
end
|
177
|
+
end # class Sync
|
178
|
+
|
179
|
+
# * Mirror side1 to side2
|
180
|
+
#
|
181
|
+
# simply follow the rule on the table below:
|
182
|
+
#
|
183
|
+
# Side 2
|
184
|
+
# |---+---------+----------+------------+---------|
|
185
|
+
# S | | M | U | N | D |
|
186
|
+
# i |---+---------+----------+------------+---------|
|
187
|
+
# d | M | OW 1->2 | OW 1->2 | CP 1->2 | CP 1->2 |
|
188
|
+
# e | U | OW 1->2 | -- | ?? -- | CP 1->2 |
|
189
|
+
# 1 | N | DEL 2 | ?? -- | -- | -- |
|
190
|
+
# | D | DEL 2 | DEL 2 | -- | -- |
|
191
|
+
# |---+---------+----------+------------+---------|
|
192
|
+
#
|
193
|
+
# + M :: Modified (or Created)
|
194
|
+
# + U :: Unchanged
|
195
|
+
# + N :: No Record
|
196
|
+
# + D :: Deleted
|
197
|
+
#
|
198
|
+
# + -- :: No operation (ignore)
|
199
|
+
# + ?? :: Not occurred in normal cases
|
200
|
+
# + OW :: Overwrite
|
201
|
+
# + CP :: Copy
|
202
|
+
# + DEL :: Delete
|
203
|
+
#
|
204
|
+
class Mirror < Base
|
205
|
+
def whatnow(side1, side2)
|
206
|
+
actions = {
|
207
|
+
"MM" => :overwrite1_to_2,
|
208
|
+
"MU" => :overwrite1_to_2,
|
209
|
+
"MN" => :copy1_to_2,
|
210
|
+
"MD" => :copy1_to_2,
|
211
|
+
|
212
|
+
"UM" => :overwrite1_to_2,
|
213
|
+
"UU" => :ignore,
|
214
|
+
"UN" => :ignore,
|
215
|
+
"UD" => :copy1_to_2,
|
216
|
+
|
217
|
+
"NM" => :delete2,
|
218
|
+
"NU" => :ignore,
|
219
|
+
"NN" => :ignore,
|
220
|
+
"ND" => :ignore,
|
221
|
+
|
222
|
+
"DM" => :delete2,
|
223
|
+
"DU" => :delete2,
|
224
|
+
"DN" => :ignore,
|
225
|
+
"DD" => :ignore,
|
226
|
+
}
|
227
|
+
return actions[status_pair(side1, side2)]
|
228
|
+
end
|
229
|
+
end # class Mirror
|
230
|
+
|
231
|
+
end # module Strategy
|
232
|
+
end # module Sync
|
233
|
+
end # module Mhc
|
@@ -0,0 +1,98 @@
|
|
1
|
+
################################################################
|
2
|
+
# Log maintenance functions.
|
3
|
+
#
|
4
|
+
# M 2000-04-25 00:06:08 <20.nom@.nomcom> ~nom/Mail/schedule/2000/04/1 Luncheon
|
5
|
+
# D 2000-04-25 00:06:08 <20.nom@.nomcom> ~nom/Mail/schedule/2000/04/1 Luncheon
|
6
|
+
# S 2000-04-25 00:06:08 user_id
|
7
|
+
#
|
8
|
+
module Mhc
|
9
|
+
class Log
|
10
|
+
|
11
|
+
def initialize(filename)
|
12
|
+
@filename = filename
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_entry(entry)
|
16
|
+
file = File.open(@filename, "a+")
|
17
|
+
file.print "#{entry}\n"
|
18
|
+
file.fsync if file.respond_to?("fsync")
|
19
|
+
file.close
|
20
|
+
end
|
21
|
+
|
22
|
+
def each_entry
|
23
|
+
begin
|
24
|
+
file = File.open(@filename)
|
25
|
+
while line = file.gets
|
26
|
+
yield(MhcLogEntry.new(line.chomp))
|
27
|
+
end
|
28
|
+
file.close
|
29
|
+
rescue
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def entries()
|
34
|
+
arry = []
|
35
|
+
each_entry{|e|
|
36
|
+
arry << e
|
37
|
+
}
|
38
|
+
return arry
|
39
|
+
end
|
40
|
+
|
41
|
+
def shrink_entries(user_id)
|
42
|
+
hash = {}
|
43
|
+
each_entry{|e|
|
44
|
+
if e.status == 'S' and e.rec_id == user_id
|
45
|
+
hash.clear
|
46
|
+
else
|
47
|
+
hash[e.rec_id] = e
|
48
|
+
end
|
49
|
+
}
|
50
|
+
return hash.values
|
51
|
+
end
|
52
|
+
end # class Log
|
53
|
+
end # module Mhc
|
54
|
+
|
55
|
+
################
|
56
|
+
module Mhc
|
57
|
+
class LogEntry
|
58
|
+
attr :status
|
59
|
+
attr :mtime
|
60
|
+
attr :rec_id
|
61
|
+
attr :path
|
62
|
+
attr :subject
|
63
|
+
|
64
|
+
def initialize(status, mtime = nil, rec_id = nil, path = nil, subject = nil)
|
65
|
+
if mtime.nil?
|
66
|
+
init_from_string(status)
|
67
|
+
else
|
68
|
+
@status, @mtime, @rec_id, @path, @subject =
|
69
|
+
status, mtime, rec_id, path, subject
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_s
|
74
|
+
return [
|
75
|
+
@status,
|
76
|
+
@mtime.strftime("%Y-%m-%d %H:%M:%S"),
|
77
|
+
@rec_id,
|
78
|
+
@path,
|
79
|
+
@subject
|
80
|
+
].join(' ')
|
81
|
+
end
|
82
|
+
|
83
|
+
################
|
84
|
+
private
|
85
|
+
################
|
86
|
+
def init_from_string(line)
|
87
|
+
str = line.chomp
|
88
|
+
status, yymmdd, hhmmss, rec_id, path, subject = str.split
|
89
|
+
yy, mm, dd = yymmdd.split('-')
|
90
|
+
h, m, s = hhmmss.split(':')
|
91
|
+
|
92
|
+
mtime = ::Time.local(yy.to_i, mm.to_i, dd.to_i,
|
93
|
+
h .to_i, m .to_i, s .to_i)
|
94
|
+
@status, @mtime, @rec_id, @path, @subject =
|
95
|
+
status, mtime, rec_id, path, subject
|
96
|
+
end
|
97
|
+
end # class LogEntry
|
98
|
+
end # module Mhc
|