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
|
+
################################################################
|
2
|
+
# config.yml file for MHC created by mhc init command.
|
3
|
+
#
|
4
|
+
<%
|
5
|
+
repository = @config[:topdir] || '~/mhc'
|
6
|
+
tzid = @config[:tzid] || 'Unknown'
|
7
|
+
-%>
|
8
|
+
---
|
9
|
+
################################################################
|
10
|
+
GENERAL:
|
11
|
+
################################################################
|
12
|
+
|
13
|
+
# TZID defines timezone of all articles.
|
14
|
+
# It must be in the form of "Asia/Tokyo".
|
15
|
+
# ``mhc init'' is clever enough to set your timezone automatically,
|
16
|
+
# but, you may want to change this value.
|
17
|
+
# Valid names for TZID is:
|
18
|
+
# http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
19
|
+
|
20
|
+
TZID: "<%= @config[:tzid] || 'Unknown' %>"
|
21
|
+
|
22
|
+
# All MHC articles and status files are placed under this directory:
|
23
|
+
|
24
|
+
REPOSITORY: "<%= @config[:topdir] || '~/mhc' %>"
|
25
|
+
|
26
|
+
################################################################
|
27
|
+
SYNC_CHANNELS:
|
28
|
+
################################################################
|
29
|
+
|
30
|
+
# Each sync channel binds two CALENDARS (see below) for
|
31
|
+
# data-synchronization. Currently, STRATEGY allows only "mirror",
|
32
|
+
# CALENDAR1 must be a local MHC calendar, and CALENDAR2 must be a
|
33
|
+
# Google Calendar. True two-way sync is not yet implemented.
|
34
|
+
|
35
|
+
- NAME: business
|
36
|
+
CALENDAR1: business
|
37
|
+
CALENDAR2: google_business
|
38
|
+
STRATEGY: mirror
|
39
|
+
|
40
|
+
- NAME: family
|
41
|
+
CALENDAR1: family
|
42
|
+
CALENDAR2: google_family
|
43
|
+
STRATEGY: mirror
|
44
|
+
|
45
|
+
################################################################
|
46
|
+
CALENDARS:
|
47
|
+
################################################################
|
48
|
+
|
49
|
+
################################################################
|
50
|
+
## MHC local
|
51
|
+
|
52
|
+
# First entry under CALENDARS: is your default calendar.
|
53
|
+
# It must be a local MHC calendar, which has ``TYPE: mhc'' entry.
|
54
|
+
|
55
|
+
# Master calendar (default)
|
56
|
+
- &mhc_default
|
57
|
+
NAME: master
|
58
|
+
TYPE: mhc
|
59
|
+
|
60
|
+
# This ``business'' is a sample calendar, which is derived from
|
61
|
+
# ``master'' calendar with some filters and modifiers. Filters hide
|
62
|
+
# or select articles from master calendar. Modifiers decorate
|
63
|
+
# articles by preset decorators: hide_details, hide_description,
|
64
|
+
# hide_location, hide_time_range. You can write your original
|
65
|
+
# decorators (written in Ruby) in plugins/ directory located at the
|
66
|
+
# same place with this file.
|
67
|
+
|
68
|
+
- <<: *mhc_default
|
69
|
+
NAME: business
|
70
|
+
FILTER: '!category:private & !subject:"TODO"'
|
71
|
+
MODIFIERS:
|
72
|
+
- hide_description
|
73
|
+
- hide_location
|
74
|
+
- replace_subject_by_category
|
75
|
+
|
76
|
+
# Another sample, supposed to be published to your family's Google
|
77
|
+
# Calendar.
|
78
|
+
- <<: *mhc_default
|
79
|
+
NAME: family
|
80
|
+
FILTER: 'category:[birthday holiday private]'
|
81
|
+
MODIFIERS:
|
82
|
+
- hide_description
|
83
|
+
|
84
|
+
################################################################
|
85
|
+
## Google Calendar
|
86
|
+
|
87
|
+
# Currently, Google Calendar stuffs work only as destinations
|
88
|
+
# to publish from local MHC calendars.
|
89
|
+
#
|
90
|
+
# Since MHC uses standard CalDAV protocol, I believe
|
91
|
+
# it works with other CalDAV-based network calendars such as
|
92
|
+
# iCloud, but I did not confirmed.
|
93
|
+
#
|
94
|
+
# Google Calendar URL for CalDAV is in the form of:
|
95
|
+
# https://www.google.com/calendar/dav/{calendar_id}/events
|
96
|
+
#
|
97
|
+
# calendar_id:
|
98
|
+
# for primary calendar is just your email address.
|
99
|
+
# for others will be in the form of:
|
100
|
+
# [long-string-of-characters]@group.calendar.google.com
|
101
|
+
#
|
102
|
+
# You can get them by clicking the down arrow next to your
|
103
|
+
# calendar at calendar.google.com and selecting 'Calendar Settings'.
|
104
|
+
# Be sure to use HTTPS in your URL, as an http address will not work.
|
105
|
+
#
|
106
|
+
# for more details about Google Calendar settings:
|
107
|
+
# http://www.google.com/support/calendar/bin/answer.py?answer=99358
|
108
|
+
# and click Sunbird section.
|
109
|
+
#
|
110
|
+
# Fill in the blanks indicated as ***, and invoke the command:
|
111
|
+
#
|
112
|
+
# % mhc sync {sync_channel_name}
|
113
|
+
#
|
114
|
+
# {sync_channel_name} must exist in the section of SYNC_CHANNELS:
|
115
|
+
#
|
116
|
+
# NOTE:
|
117
|
+
# Articles between past 90 days and future 90 days in MHC will be
|
118
|
+
# published to the target Google Calendar. 90 is hard-coded in
|
119
|
+
# lib/mhc/calendar.rb for now. Other articles out of the range in
|
120
|
+
# Google Calendar will be DELETED.
|
121
|
+
#
|
122
|
+
# Before the first try, I recommend you to make a new calendar on your
|
123
|
+
# Google Calendar dedicated to MHC. and backup your local spool of
|
124
|
+
# MHC.
|
125
|
+
|
126
|
+
# Displayname: MHC
|
127
|
+
- &google_default
|
128
|
+
NAME: google_personal
|
129
|
+
TYPE: caldav
|
130
|
+
USER: "***@gmail.com"
|
131
|
+
PASSWORD: "***"
|
132
|
+
URL: "https://calendar.google.com/calendar/dav/***@group.calendar.google.com/events/"
|
133
|
+
|
134
|
+
# Displayname: For Colleagues
|
135
|
+
- <<: *google_default
|
136
|
+
NAME: google_business
|
137
|
+
URL: "https://calendar.google.com/calendar/dav/***@group.calendar.google.com/events/"
|
138
|
+
|
139
|
+
# Displayname: For Family
|
140
|
+
- <<: *google_default
|
141
|
+
NAME: google_family
|
142
|
+
URL: "https://calendar.google.com/calendar/dav/***@group.calendar.google.com/events/"
|
data/lib/mhc/version.rb
ADDED
data/lib/mhc/webdav.rb
ADDED
@@ -0,0 +1,319 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "net/https"
|
4
|
+
require "uri"
|
5
|
+
require "rexml/document"
|
6
|
+
require "fileutils"
|
7
|
+
|
8
|
+
module Mhc
|
9
|
+
class WebDav
|
10
|
+
# WebDAV protocol: RFC4918
|
11
|
+
# see http://tools.ietf.org/html/rfc4918
|
12
|
+
#
|
13
|
+
class Client
|
14
|
+
attr_reader :top_directory
|
15
|
+
|
16
|
+
def initialize(base_url, proxy_host = nil, proxy_port = nil)
|
17
|
+
uri = URI.parse(base_url)
|
18
|
+
@top_directory = uri.path
|
19
|
+
@http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port)
|
20
|
+
@http.use_ssl = true if uri.scheme == "https"
|
21
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_basic_auth(user, password)
|
25
|
+
@auth_user, @auth_password = user, password
|
26
|
+
return self
|
27
|
+
end
|
28
|
+
|
29
|
+
# 8.1 PROPFIND
|
30
|
+
def propfind(path = @top_directory, depth = 1, xml_body = nil)
|
31
|
+
req = setup_request(Net::HTTP::Propfind, path)
|
32
|
+
req['Depth'] = depth
|
33
|
+
|
34
|
+
if xml_body
|
35
|
+
req.content_type = 'application/xml; charset="utf-8"'
|
36
|
+
req.content_length = xml_body.size
|
37
|
+
req.body = xml_body
|
38
|
+
end
|
39
|
+
|
40
|
+
res = @http.request(req)
|
41
|
+
|
42
|
+
if $MHC_DEBUG
|
43
|
+
STDERR.print "\n* PROPFIND RESPONSE:\n"
|
44
|
+
STDERR.print dump_response(res, true)
|
45
|
+
end
|
46
|
+
|
47
|
+
check_status_code(res, 207) # Multi-Status
|
48
|
+
|
49
|
+
return res
|
50
|
+
end
|
51
|
+
|
52
|
+
# 8.2 PROPPATCH
|
53
|
+
def proppatch
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
|
57
|
+
# 8.3 MKCOL
|
58
|
+
def mkcol(path)
|
59
|
+
req = setup_request(Net::HTTP::Mkcol, path)
|
60
|
+
res = @http.request(req)
|
61
|
+
check_status_code(res, 201) # Created
|
62
|
+
return res
|
63
|
+
end
|
64
|
+
|
65
|
+
# 8.4 GET
|
66
|
+
def get(path)
|
67
|
+
req = setup_request(Net::HTTP::Get, path)
|
68
|
+
res = @http.request(req)
|
69
|
+
check_status_code(res, 200) # OK
|
70
|
+
return res
|
71
|
+
end
|
72
|
+
|
73
|
+
# 8.4 HEAD
|
74
|
+
def head(path)
|
75
|
+
req = setup_request(Net::HTTP::Head, path)
|
76
|
+
res = @http.request(req)
|
77
|
+
check_status_code(res, 200) # OK
|
78
|
+
return res
|
79
|
+
end
|
80
|
+
|
81
|
+
# 8.5 POST
|
82
|
+
def post(content, dest_path)
|
83
|
+
req = setup_request(Net::HTTP::Post, dest_path)
|
84
|
+
req.content_length = content.size
|
85
|
+
req.body = content
|
86
|
+
res = @http.request(req)
|
87
|
+
|
88
|
+
check_status_code(res, [201, 204]) # Created or No content
|
89
|
+
return res
|
90
|
+
end
|
91
|
+
|
92
|
+
# 8.6 DELETE
|
93
|
+
def delete(path, ifmatch = nil)
|
94
|
+
req = setup_request(Net::HTTP::Delete, path)
|
95
|
+
req['If-Match'] = ifmatch if ifmatch
|
96
|
+
res = @http.request(req)
|
97
|
+
|
98
|
+
check_status_code(res, 204)
|
99
|
+
return res
|
100
|
+
end
|
101
|
+
|
102
|
+
# 8.7 PUT
|
103
|
+
def put(content, dest_path, ifmatch = nil)
|
104
|
+
req = setup_request(Net::HTTP::Put, dest_path)
|
105
|
+
req.content_length = content.size
|
106
|
+
req['If-Match'] = ifmatch if ifmatch
|
107
|
+
req.content_type = "text/calendar; charset=utf-8" # xxx
|
108
|
+
req.body = content
|
109
|
+
res = @http.request(req)
|
110
|
+
|
111
|
+
if $MHC_DEBUG
|
112
|
+
STDERR.print "\n* PUT RESPONSE:\n"
|
113
|
+
STDERR.print dump_response(res)
|
114
|
+
STDERR.print "* HEAD RESPONSE:\n"
|
115
|
+
STDERR.print dump_response(head(dest_path))
|
116
|
+
end
|
117
|
+
|
118
|
+
check_status_code(res, [201, 204]) # Created or No content
|
119
|
+
return res
|
120
|
+
end
|
121
|
+
|
122
|
+
# 8.8 COPY
|
123
|
+
def copy(src_path, dest_path)
|
124
|
+
req = setup_request(Net::HTTP::Copy, src_path)
|
125
|
+
req['Destination'] = dest_path
|
126
|
+
|
127
|
+
res = @http.request(req)
|
128
|
+
check_status_code(res, 204) # No Content
|
129
|
+
return res
|
130
|
+
end
|
131
|
+
|
132
|
+
# 8.9 MOVE
|
133
|
+
def move(src_path, dest_path)
|
134
|
+
req = setup_request(Net::HTTP::Move, src_path)
|
135
|
+
req['Destination'] = dest_path
|
136
|
+
|
137
|
+
res = @http.request(req)
|
138
|
+
check_status_code(res, 204) # No Content
|
139
|
+
return res
|
140
|
+
end
|
141
|
+
|
142
|
+
# 8.10 LOCK
|
143
|
+
def lock(path)
|
144
|
+
raise NotImplementedError
|
145
|
+
end
|
146
|
+
|
147
|
+
# 8.11 UNLOCK
|
148
|
+
def unlock(path)
|
149
|
+
raise NotImplementedError
|
150
|
+
end
|
151
|
+
|
152
|
+
################################################################
|
153
|
+
private
|
154
|
+
|
155
|
+
def check_status_code(res, required_status)
|
156
|
+
unless ([required_status].flatten.map{|c| c.to_s}).member?(res.code)
|
157
|
+
header = "Invalid HttpResponse"
|
158
|
+
raise Exception.new("#{res.code} #{header} #{res.message} #{res.body}")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def setup_request(request, *args)
|
163
|
+
req = request.new(*args)
|
164
|
+
req.basic_auth @auth_user, @auth_password
|
165
|
+
|
166
|
+
# XXX: should implement re-connection mechanism for Keep-Alive:
|
167
|
+
# http://d.hatena.ne.jp/daftbeats/20080321/1206092975
|
168
|
+
req["Connection"] = "Keep-Alive"
|
169
|
+
|
170
|
+
return req
|
171
|
+
end
|
172
|
+
|
173
|
+
def fetch(uri_str, limit = 10)
|
174
|
+
raise StandardError, 'HTTP redirect too deep' if limit == 0
|
175
|
+
|
176
|
+
response = Net::HTTP.get_response(URI.parse(uri_str))
|
177
|
+
case response
|
178
|
+
when Net::HTTPSuccess
|
179
|
+
response
|
180
|
+
when Net::HTTPRedirection
|
181
|
+
fetch(response['location'], limit - 1)
|
182
|
+
else
|
183
|
+
response.value
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def dump_response(res, include_body = false)
|
188
|
+
string = ""
|
189
|
+
|
190
|
+
res.each do |name, value|
|
191
|
+
string += " #{name}: #{value}\n"
|
192
|
+
end
|
193
|
+
string += res.body + "\n" if include_body
|
194
|
+
|
195
|
+
return string
|
196
|
+
end
|
197
|
+
|
198
|
+
end # class Client
|
199
|
+
|
200
|
+
class Cache
|
201
|
+
class DirectoryNotFoundError < StandardError
|
202
|
+
end
|
203
|
+
|
204
|
+
def initialize(top_directory)
|
205
|
+
set_top_directory(top_directory)
|
206
|
+
end
|
207
|
+
|
208
|
+
def set_top_directory(path)
|
209
|
+
raise DirectoryNotFoundError unless File.directory?(path)
|
210
|
+
@local_top_pathname = Pathname.new(path)
|
211
|
+
return self
|
212
|
+
end
|
213
|
+
|
214
|
+
def set_propfind_cache(path, xml)
|
215
|
+
File.open(local_cache_path(path), "w") do |f|
|
216
|
+
f.write(xml)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def set_basic_auth(user, password)
|
221
|
+
# nothing to do
|
222
|
+
return self
|
223
|
+
end
|
224
|
+
|
225
|
+
# 8.1 PROPFIND
|
226
|
+
def propfind(path, depth = 1, xml_body = nil)
|
227
|
+
File.read(local_cache_path(path)) rescue nil
|
228
|
+
end
|
229
|
+
|
230
|
+
# 8.2 PROPPATCH
|
231
|
+
def proppatch
|
232
|
+
raise NotImplementedError
|
233
|
+
end
|
234
|
+
|
235
|
+
# 8.3 MKCOL
|
236
|
+
def mkcol(path)
|
237
|
+
File.mkdir(local_path(path))
|
238
|
+
end
|
239
|
+
|
240
|
+
# 8.4 GET
|
241
|
+
def get(path)
|
242
|
+
File.read(local_path(path))
|
243
|
+
end
|
244
|
+
|
245
|
+
# 8.4 HEAD
|
246
|
+
def head(path)
|
247
|
+
raise NotImplementedError
|
248
|
+
end
|
249
|
+
|
250
|
+
# 8.5 POST
|
251
|
+
def post(content, dest_path)
|
252
|
+
raise NotImplementedError
|
253
|
+
end
|
254
|
+
|
255
|
+
# 8.6 DELETE
|
256
|
+
def delete(path)
|
257
|
+
File.unlink(local_path(path))
|
258
|
+
end
|
259
|
+
|
260
|
+
# 8.7 PUT
|
261
|
+
def put(content, dest_path)
|
262
|
+
make_directory_or_higher(File.dirname(local_path(dest_path)))
|
263
|
+
|
264
|
+
File.open(local_path(dest_path), "w") do |f|
|
265
|
+
f.write(content)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# 8.8 COPY
|
270
|
+
def copy(src_path, dest_path)
|
271
|
+
raise NotImplementedError
|
272
|
+
end
|
273
|
+
|
274
|
+
# 8.9 MOVE
|
275
|
+
def move(src_path, dest_path)
|
276
|
+
raise NotImplementedError
|
277
|
+
end
|
278
|
+
|
279
|
+
# 8.10 LOCK
|
280
|
+
def lock(path)
|
281
|
+
raise NotImplementedError
|
282
|
+
end
|
283
|
+
|
284
|
+
# 8.11 UNLOCK
|
285
|
+
def unlock(path)
|
286
|
+
raise NotImplementedError
|
287
|
+
end
|
288
|
+
|
289
|
+
private
|
290
|
+
|
291
|
+
def make_directory_or_higher(directory)
|
292
|
+
unless File.directory?(directory)
|
293
|
+
parent = File.dirname(directory)
|
294
|
+
make_directory_or_higher(parent)
|
295
|
+
print "mkdir #{directory}\n"
|
296
|
+
return Dir.mkdir(directory)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def local_pathname(path)
|
301
|
+
pathname = Pathname.new(path)
|
302
|
+
raise "path (#{path.to_s})should be absolute." unless pathname.absolute?
|
303
|
+
(@local_top_pathname + ("./" + pathname)).cleanpath
|
304
|
+
end
|
305
|
+
|
306
|
+
def local_path(path)
|
307
|
+
local_pathname(path).to_s
|
308
|
+
end
|
309
|
+
|
310
|
+
def local_cache_path(path)
|
311
|
+
if File.directory?(local_path(path))
|
312
|
+
(local_pathname(path) + "propfind-cache.xml").cleanpath.to_s
|
313
|
+
else
|
314
|
+
local_path(path)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end # class Cache
|
318
|
+
end # class WebDav
|
319
|
+
end # module Mhc
|