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,45 @@
|
|
1
|
+
class DateTime
|
2
|
+
alias_method :to_mhc_string, :to_s
|
3
|
+
end
|
4
|
+
|
5
|
+
module Mhc
|
6
|
+
module PropertyValue
|
7
|
+
class Time < Base
|
8
|
+
include Comparable
|
9
|
+
|
10
|
+
def parse(string)
|
11
|
+
if /^(\d+):(\d+)$/ =~ string
|
12
|
+
@sec = ($1.to_i) * 3600 + ($2.to_i) * 60
|
13
|
+
end
|
14
|
+
return self
|
15
|
+
end
|
16
|
+
|
17
|
+
def days; (@sec ) / 86400 ;end
|
18
|
+
def hour; (@sec % 86400) / 3600 ;end
|
19
|
+
def minute; (@sec % 3600) / 60 ;end
|
20
|
+
|
21
|
+
def <=>(o)
|
22
|
+
return @sec <=> o.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_mhc_string
|
26
|
+
return format("%02d:%02d", hour, minute)
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :to_s, :to_mhc_string
|
30
|
+
|
31
|
+
def to_i
|
32
|
+
return @sec
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_a
|
36
|
+
return [hour, minute]
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_datetime(date = Mhc::PropertyValue::Date.new(1970, 1, 2))
|
40
|
+
date = date + days
|
41
|
+
time = ::DateTime.new(date.year, date.month, date.day, hour, minute, 0, DateTime.now.zone) # make local DateTime
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end # module PropertyValue
|
45
|
+
end # module Mhc
|
data/lib/mhc/query.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
module Mhc
|
2
|
+
class Query
|
3
|
+
def initialize(query_string)
|
4
|
+
@expression = Expression.new(Context.new(query_string))
|
5
|
+
@query_string = query_string
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_proc
|
9
|
+
return @expression.to_proc
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
@query_string.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
class ParseError < StandardError; end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Expression :: Term ('|' Term)*
|
20
|
+
#
|
21
|
+
class Expression
|
22
|
+
def initialize(context)
|
23
|
+
@terms = [Term.new(context)]
|
24
|
+
@terms << Term.new(context) while context.eat_if(:orop)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_proc
|
28
|
+
@procs = @terms.map(&:to_proc)
|
29
|
+
return lambda {|ev| @procs.any? {|p| p.call(ev)}}
|
30
|
+
end
|
31
|
+
end # class Expression
|
32
|
+
|
33
|
+
#
|
34
|
+
# Term :: Factor ('&' Factor)*
|
35
|
+
#
|
36
|
+
class Term
|
37
|
+
def initialize(context)
|
38
|
+
@factors = [Factor.new(context)]
|
39
|
+
@factors << Factor.new(context) while context.eat_if(:andop)
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_proc
|
43
|
+
@procs = @factors.map(&:to_proc)
|
44
|
+
return lambda {|ev| @procs.all? {|p| p.call(ev)}}
|
45
|
+
end
|
46
|
+
end # class Term
|
47
|
+
|
48
|
+
#
|
49
|
+
# Factor :: '!'* ( '(' Expression ')' || RelationalExpression )
|
50
|
+
#
|
51
|
+
class Factor
|
52
|
+
def initialize(context)
|
53
|
+
@expected_value = true
|
54
|
+
@expected_value = !@expected_value while context.eat_if(:negop)
|
55
|
+
|
56
|
+
if context.eat_if(:lparen)
|
57
|
+
@value = Expression.new(context)
|
58
|
+
context.expect(:rparen)
|
59
|
+
else
|
60
|
+
@value = RelationalExpression.new(context)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_proc
|
65
|
+
@proc = @value.to_proc
|
66
|
+
return lambda {|ev| @proc.call(ev) == @expected_value}
|
67
|
+
end
|
68
|
+
end # class Factor
|
69
|
+
|
70
|
+
#
|
71
|
+
# RelationalExpression :: Symbol Operator (Argument || '[' Argument Argument* ']')
|
72
|
+
#
|
73
|
+
class RelationalExpression
|
74
|
+
KEYWORDS = [:subject, :category, :body, :location, :recurrence_tag]
|
75
|
+
|
76
|
+
def initialize(context)
|
77
|
+
@name = context.expect(:symbol).value.downcase.to_sym
|
78
|
+
raise ParseError, "unknown keyword '#{@name}'" unless KEYWORDS.member?(@name)
|
79
|
+
|
80
|
+
context.expect(:sepop) # Currently, operator is only ":"
|
81
|
+
|
82
|
+
@arguments = []
|
83
|
+
if context.eat_if(:lbracket)
|
84
|
+
loop do
|
85
|
+
@arguments << Argument.new(context)
|
86
|
+
break if context.eat_if(:rbracket)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
@arguments << Argument.new(context)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_proc
|
94
|
+
case @name
|
95
|
+
when :category
|
96
|
+
@arguments = @arguments.map{|arg| arg.value.downcase}
|
97
|
+
return lambda {|ev| !(ev.categories.map{|c| c.to_s.downcase} & @arguments).empty?}
|
98
|
+
when :recurrence_tag
|
99
|
+
@arguments = @arguments.map{|arg| arg.value.downcase}
|
100
|
+
return lambda {|ev| !!@arguments.find{|v| ev.send(@name).to_s.downcase.toutf8 == v}}
|
101
|
+
else
|
102
|
+
@arguments = @arguments.map{|arg| Regexp.quote(arg.value)}
|
103
|
+
return lambda {|ev| !!@arguments.find{|v| ev.send(@name).to_s.toutf8.match(v)}}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end # class RelationalExpression
|
107
|
+
|
108
|
+
#
|
109
|
+
# Argument :: Symbol || String
|
110
|
+
#
|
111
|
+
class Argument
|
112
|
+
def initialize(context)
|
113
|
+
token = context.expect(:symbol, :string)
|
114
|
+
@type = token.type
|
115
|
+
@value = token.value
|
116
|
+
end
|
117
|
+
|
118
|
+
def value
|
119
|
+
case @type
|
120
|
+
when :string
|
121
|
+
@value[1..-2]
|
122
|
+
else
|
123
|
+
@value
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end # class Argument
|
127
|
+
|
128
|
+
class Context
|
129
|
+
TOKENS = {
|
130
|
+
symbol: /[a-zA-Z_][a-zA-Z_\d]*/,
|
131
|
+
string: /"(?:[^"\\]|\\.)*"/,
|
132
|
+
negop: /!/,
|
133
|
+
andop: /&/,
|
134
|
+
orop: /\|/,
|
135
|
+
sepop: /:/,
|
136
|
+
lparen: /\(/,
|
137
|
+
rparen: /\)/,
|
138
|
+
lbracket: /\[/,
|
139
|
+
rbracket: /\]/
|
140
|
+
}.map{|type,regexp| "(?<#{type}>#{regexp})"}.join("|")
|
141
|
+
|
142
|
+
TOKEN_REGEXP = Regexp.new('^\s*(' + TOKENS + ')')
|
143
|
+
|
144
|
+
def initialize(string)
|
145
|
+
@tokens = tokenize(string)
|
146
|
+
end
|
147
|
+
|
148
|
+
def eat_if(*expected_types)
|
149
|
+
expected_types.each do |expected_type|
|
150
|
+
if @tokens.first and @tokens.first.type == expected_type
|
151
|
+
return @tokens.shift
|
152
|
+
end
|
153
|
+
end
|
154
|
+
return nil
|
155
|
+
end
|
156
|
+
|
157
|
+
def expect(*expected_types)
|
158
|
+
token = eat_if(*expected_types) and return token
|
159
|
+
raise ParseError, "#{expected_types.map(&:upcase).join(' or ')} expected before #{@tokens.first.value rescue 'END'}"
|
160
|
+
end
|
161
|
+
|
162
|
+
def debug_dump
|
163
|
+
@tokens.map{|token| "#{token.type} => #{token.value}"}.join(", ")
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def tokenize(string)
|
169
|
+
tokens = []
|
170
|
+
|
171
|
+
loop do
|
172
|
+
token, string = get_token(string)
|
173
|
+
break if token.nil?
|
174
|
+
tokens << token
|
175
|
+
end
|
176
|
+
|
177
|
+
raise ParseError, "can not tokenize '#{string}'" unless string.length == 0
|
178
|
+
return tokens
|
179
|
+
end
|
180
|
+
|
181
|
+
def get_token(string)
|
182
|
+
if match = TOKEN_REGEXP.match(string)
|
183
|
+
name = match.names.find{|name| match[name]}
|
184
|
+
value = match[name]
|
185
|
+
remain = match.post_match.strip
|
186
|
+
return [Token.new(name, value), remain]
|
187
|
+
end
|
188
|
+
return [nil, string]
|
189
|
+
end
|
190
|
+
end # class Context
|
191
|
+
|
192
|
+
class Token
|
193
|
+
attr_reader :type, :value
|
194
|
+
|
195
|
+
def initialize(type, string)
|
196
|
+
@type, @value = type.to_sym, string
|
197
|
+
end
|
198
|
+
end # class Token
|
199
|
+
|
200
|
+
class Test
|
201
|
+
attr_reader :categories, :subject
|
202
|
+
|
203
|
+
def initialize(categories = [], subject = "", body = "")
|
204
|
+
@categories = categories
|
205
|
+
@subject = subject
|
206
|
+
@body = body
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
data/lib/mhc/sync.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
################################################################
|
2
|
+
#
|
3
|
+
# We assume a CalDAV server as a remote side.
|
4
|
+
# CalDAV has no ability to provide such information.
|
5
|
+
# It only provides ETag mechanism, which is defined
|
6
|
+
# in HTTP protocol (see rfc2616).
|
7
|
+
#
|
8
|
+
# So, we have to maintain a ETag cache (replica) on local and
|
9
|
+
# manage difference between the cache and the remote.
|
10
|
+
# Using the ETag information:
|
11
|
+
#
|
12
|
+
# (1) get uid-etag list via PROPFIND (WebDav) method
|
13
|
+
#
|
14
|
+
# make a set: R_SET = [(r_uid, r_etag)..]
|
15
|
+
# r_uid: unique id of a remote article.
|
16
|
+
# r_etag: corresponding etag of r_uid.
|
17
|
+
#
|
18
|
+
# (2) get uid-etag list via local cache.
|
19
|
+
#
|
20
|
+
# make a set: L_SET = [(l_uid, l_etag)..]
|
21
|
+
#
|
22
|
+
# (3) for each uid: [uid| (uid, etag) <- L_SET + R_SET]
|
23
|
+
#
|
24
|
+
# (A) if (uid, _) is missed in L_SET
|
25
|
+
# -> SET_MARK(uid, M)
|
26
|
+
#
|
27
|
+
# (B) if (uid, _) is missed in R_SET
|
28
|
+
# -> SET_MARK(uid, D)
|
29
|
+
#
|
30
|
+
# (C) if (uid, _) exists in both R_SET and L_SET
|
31
|
+
# if l_etag != r_etag
|
32
|
+
# -> SET_MARK(uid, M)
|
33
|
+
# if l_etag == r_etag
|
34
|
+
# -> SET_MARK(uid, N)
|
35
|
+
#
|
36
|
+
module Mhc
|
37
|
+
module Sync
|
38
|
+
dir = File.dirname(__FILE__) + "/sync"
|
39
|
+
|
40
|
+
autoload :Driver, "#{dir}/driver.rb"
|
41
|
+
autoload :Status, "#{dir}/status.rb"
|
42
|
+
autoload :StatusManager, "#{dir}/status_manager.rb"
|
43
|
+
autoload :Strategy, "#{dir}/strategy.rb"
|
44
|
+
autoload :Log, "#{dir}/syncinfo.rb"
|
45
|
+
end # module Sync
|
46
|
+
end # module Mhc
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Mhc
|
2
|
+
module Sync
|
3
|
+
##
|
4
|
+
# Sync Driver takes two calendar databases to sync.
|
5
|
+
#
|
6
|
+
# Each record in calendar has to respond to:
|
7
|
+
# * Record#unmodified?
|
8
|
+
# * Record#deleted?
|
9
|
+
# * Record#etag
|
10
|
+
# * Record#etag=
|
11
|
+
# * Record#ex_etag
|
12
|
+
#
|
13
|
+
class Driver
|
14
|
+
def initialize(db1, db2, strategy)
|
15
|
+
@db1 = db1
|
16
|
+
@db2 = db2
|
17
|
+
@strategy = Strategy::Factory.create(strategy)
|
18
|
+
end
|
19
|
+
|
20
|
+
def sync_all(dry_run = false, max_count = 50)
|
21
|
+
list_cache = uid_list
|
22
|
+
|
23
|
+
items = count_sync_items(list_cache)
|
24
|
+
if items > max_count
|
25
|
+
STDERR.print "Too many (#{items}) articles to sync... abort\n"
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
|
29
|
+
list_cache.each do |uid|
|
30
|
+
sync(uid, dry_run)
|
31
|
+
end
|
32
|
+
|
33
|
+
return true
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def count_sync_items(sync_uid_list)
|
39
|
+
sync_uid_list.map{|uid| sync(uid, true, true)}.count{|s| s != :ignore}
|
40
|
+
end
|
41
|
+
|
42
|
+
def sync(uid, dry_run = false, quiet = false)
|
43
|
+
info1 = @db1.syncinfo(uid)
|
44
|
+
info2 = @db2.syncinfo(uid)
|
45
|
+
|
46
|
+
unless @strategy.whatnow(info1, info2) == :ignore or quiet
|
47
|
+
STDERR.print "ABOUT#{dry_run ? '(DRY_RUN)' : ''} #{uid} => #{@strategy.whatnow(info1, info2)} "
|
48
|
+
STDERR.print "(#{info1.sync_status} vs #{info2.sync_status})\n"
|
49
|
+
end
|
50
|
+
return @strategy.whatnow(info1, info2) if dry_run
|
51
|
+
|
52
|
+
case @strategy.whatnow(info1, info2)
|
53
|
+
when :ignore
|
54
|
+
#ignore(side1, side2)
|
55
|
+
when :conflict
|
56
|
+
merge(uid, @db1, @db2)
|
57
|
+
when :delete1
|
58
|
+
delete(uid, @db1, @db2)
|
59
|
+
when :delete2
|
60
|
+
delete(uid, @db2, @db1)
|
61
|
+
when :copy1_to_2
|
62
|
+
copy(uid, @db1, @db2)
|
63
|
+
when :copy2_to_1
|
64
|
+
copy(uid, @db2, @db1)
|
65
|
+
when :overwrite1_to_2
|
66
|
+
copy(uid, @db1, @db2, :overwrite)
|
67
|
+
when :overwrite2_to_1
|
68
|
+
copy(uid, @db2, @db1, :overwrite)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def uid_list
|
73
|
+
(@db1.uid_list + @db2.uid_list).sort.uniq
|
74
|
+
end
|
75
|
+
|
76
|
+
def merge(uid, db1, db2)
|
77
|
+
# Not yet implemented
|
78
|
+
s1 = db1.get(uid)
|
79
|
+
s2 = db2.get(uid)
|
80
|
+
STDERR.print("Conflict: UID=#{uid} ... did nothing.\n")
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def delete(uid, db, db2)
|
85
|
+
info = db.syncinfo(uid)
|
86
|
+
info2 = db2.syncinfo(uid)
|
87
|
+
if db.delete(uid)
|
88
|
+
info.mark_synced(nil)
|
89
|
+
info2.mark_synced(nil)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def copy(uid, db1, db2, overwrite = false)
|
94
|
+
ev = db1.get(uid)
|
95
|
+
STDERR.print "COPYING:#{overwrite ? ' (overwrite)' : ''} #{ev.uid}\n"
|
96
|
+
|
97
|
+
db2.delete(uid) if overwrite
|
98
|
+
|
99
|
+
if new_info = db2.put(ev, overwrite)
|
100
|
+
db1.syncinfo(uid).mark_synced(ev.etag)
|
101
|
+
db2.syncinfo(uid).mark_synced(new_info.etag)
|
102
|
+
else
|
103
|
+
STDERR.print "COPY: failed.\n"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end # class Driver
|
107
|
+
end # module Sync
|
108
|
+
end # module Mhc
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Mhc
|
2
|
+
module Sync
|
3
|
+
##
|
4
|
+
# status
|
5
|
+
#
|
6
|
+
class Status
|
7
|
+
def initialize(uid, manager, wrapped_record = nil)
|
8
|
+
@uid, @manager, @wrapped_record = uid, manager, wrapped_record
|
9
|
+
end
|
10
|
+
|
11
|
+
def uid
|
12
|
+
return @uid
|
13
|
+
end
|
14
|
+
|
15
|
+
def etag
|
16
|
+
@manager.etag(@uid)
|
17
|
+
end
|
18
|
+
|
19
|
+
def ex_etag
|
20
|
+
@manager.ex_etag(@uid)
|
21
|
+
end
|
22
|
+
|
23
|
+
def sync_status
|
24
|
+
return :norecord if !etag and !ex_etag
|
25
|
+
return :created if etag and !ex_etag
|
26
|
+
return :deleted if !etag and ex_etag
|
27
|
+
return :unmodified if etag == ex_etag
|
28
|
+
return :modified if etag != ex_etag
|
29
|
+
end
|
30
|
+
|
31
|
+
def modified?
|
32
|
+
sync_status == :created or sync_status == :modified
|
33
|
+
end
|
34
|
+
|
35
|
+
def unmodified?
|
36
|
+
sync_status == :unmodified
|
37
|
+
end
|
38
|
+
|
39
|
+
def norecord?
|
40
|
+
sync_status == :norecord
|
41
|
+
end
|
42
|
+
|
43
|
+
def deleted?
|
44
|
+
sync_status == :deleted
|
45
|
+
end
|
46
|
+
|
47
|
+
def mark_synced(etag = self.etag)
|
48
|
+
@manager.mark_synced(uid, etag)
|
49
|
+
return self
|
50
|
+
end
|
51
|
+
|
52
|
+
### as a calendar DB redord
|
53
|
+
def to_ics_string
|
54
|
+
# LastNote or mhc is assumed.
|
55
|
+
if @wrapped_record.respond_to?(:to_ics_string)
|
56
|
+
result = @wrapped_record.to_ics_string
|
57
|
+
return result
|
58
|
+
end
|
59
|
+
|
60
|
+
# HTTP::Response from caldav server is assumed.
|
61
|
+
if @wrapped_record.respond_to?(:body)
|
62
|
+
return @wrapped_record.body
|
63
|
+
end
|
64
|
+
|
65
|
+
return nil # Nil or unsupport object class. XXX donot put.
|
66
|
+
end
|
67
|
+
|
68
|
+
end # class Status
|
69
|
+
end # module Sync
|
70
|
+
end # module Mhc
|