Class: Async::Caldav::Storage::Filesystem

Inherits:
Protocol::Caldav::Storage
  • Object
show all
Defined in:
lib/async/caldav/storage/filesystem.rb

Instance Method Summary collapse

Constructor Details

#initialize(root) ⇒ Filesystem

Returns a new instance of Filesystem.



10
11
12
13
14
15
16
17
18
19
20
# File 'lib/async/caldav/storage/filesystem.rb', line 10

def initialize(root)
  @root = root
  @sync_snapshots = {}
  FileUtils.mkdir_p(@root)
  @root_normalized = ::File.expand_path(@root)
  if @root_normalized.end_with?(::File::SEPARATOR)
    @root_with_separator = @root_normalized
  else
    @root_with_separator = "#{@root_normalized}#{::File::SEPARATOR}"
  end
end

Instance Method Details

#collection_exists?(path) ⇒ Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/async/caldav/storage/filesystem.rb', line 104

def collection_exists?(path)
  File.exist?(File.join(full_path(path), ".collection.json"))
end

#create_collection(path, props = {}) ⇒ Object

--- Collections ---



24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/async/caldav/storage/filesystem.rb', line 24

def create_collection(path, props = {})
  dir = full_path(path)
  FileUtils.mkdir_p(dir)
  meta = {
    "type"        => (props[:type] || :collection).to_s,
    "displayname" => props[:displayname],
    "description" => props[:description],
    "color"       => props[:color],
    "props"       => props[:props] || {},
  }
  File.write(File.join(dir, ".collection.json"), JSON.pretty_generate(meta))
  symbolize(meta)
end

#delete_collection(path) ⇒ Object



47
48
49
50
51
52
53
54
55
# File 'lib/async/caldav/storage/filesystem.rb', line 47

def delete_collection(path)
  s = stat(path)
  if s && s.directory?
    FileUtils.rm_rf(full_path(path))
    true
  else
    false
  end
end

#delete_item(path) ⇒ Object



132
133
134
135
136
137
138
139
140
# File 'lib/async/caldav/storage/filesystem.rb', line 132

def delete_item(path)
  s = stat(path)
  if s && s.file?
    File.delete(full_path(path))
    true
  else
    false
  end
end

#etag(path) ⇒ Object



191
192
193
194
# File 'lib/async/caldav/storage/filesystem.rb', line 191

def etag(path)
  item = get_item(path)
  item ? item[:etag] : nil
end

#exists?(path) ⇒ Boolean

--- General ---

Returns:

  • (Boolean)


183
184
185
186
187
188
189
# File 'lib/async/caldav/storage/filesystem.rb', line 183

def exists?(path)
  if stat(path)
    true
  else
    collection_exists?(path)
  end
end

#get_collection(path) ⇒ Object



38
39
40
41
42
43
44
45
# File 'lib/async/caldav/storage/filesystem.rb', line 38

def get_collection(path)
  meta_file = File.join(full_path(path), ".collection.json")
  if File.exist?(meta_file)
    symbolize(JSON.parse(File.read(meta_file)))
  else
    nil
  end
end

#get_item(path) ⇒ Object

--- Items ---



110
111
112
113
114
115
116
117
118
119
120
# File 'lib/async/caldav/storage/filesystem.rb', line 110

def get_item(path)
  s = stat(path)
  if s && s.file?
    body = File.read(full_path(path))
    content_type = guess_content_type(path)
    etag = Protocol::Caldav::ETag.compute(body)
    { body: body, content_type: content_type, etag: etag }
  else
    nil
  end
end

#get_multi(paths) ⇒ Object



177
178
179
# File 'lib/async/caldav/storage/filesystem.rb', line 177

def get_multi(paths)
  paths.map { |p| [p, get_item(p)] }
end

#list_collections(parent_path) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/async/caldav/storage/filesystem.rb', line 57

def list_collections(parent_path)
  dir = full_path(parent_path)
  if File.directory?(dir)
    Dir.children(dir).filter_map do |name|
      child_dir = File.join(dir, name)
      meta_file = File.join(child_dir, ".collection.json")
      unless File.directory?(child_dir) && File.exist?(meta_file)
        next
      end

      child_path = File.join(parent_path, name).sub(%r{/*$}, "/")
      [child_path, symbolize(JSON.parse(File.read(meta_file)))]
    end
  else
    []
  end
end

#list_items(collection_path) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/async/caldav/storage/filesystem.rb', line 142

def list_items(collection_path)
  dir = full_path(collection_path)
  if File.directory?(dir)
    Dir.children(dir).filter_map do |name|
      if name.start_with?(".")
        next
      end
      file = File.join(dir, name)
      unless File.file?(file)
        next
      end

      item_path = File.join(collection_path, name)
      body = File.read(file)
      content_type = guess_content_type(name)
      etag = Protocol::Caldav::ETag.compute(body)
      [item_path, { body: body, content_type: content_type, etag: etag }]
    end
  else
    []
  end
end

#move_item(from_path, to_path) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/async/caldav/storage/filesystem.rb', line 165

def move_item(from_path, to_path)
  s = stat(from_path)
  if s && s.file?
    dst = full_path(to_path)
    FileUtils.mkdir_p(File.dirname(dst))
    FileUtils.mv(full_path(from_path), dst)
    get_item(to_path)
  else
    nil
  end
end

#put_item(path, body, content_type) ⇒ Object



122
123
124
125
126
127
128
129
130
# File 'lib/async/caldav/storage/filesystem.rb', line 122

def put_item(path, body, content_type)
  file = full_path(path)
  is_new = !File.exist?(file)
  FileUtils.mkdir_p(File.dirname(file))
  File.write(file, body)
  etag = Protocol::Caldav::ETag.compute(body)
  item = { body: body, content_type: content_type, etag: etag }
  [item, is_new]
end

#snapshot_sync(collection_path) ⇒ Object

--- Sync ---



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/async/caldav/storage/filesystem.rb', line 198

def snapshot_sync(collection_path)
  items = list_items(collection_path)
  snapshot = {}
  items.each { |path, data| snapshot[path] = data[:etag] }

  col = get_collection(collection_path) || {}
  item_etags = items.map { |_, data| data[:etag] }
  ctag = Protocol::Caldav::CTag.compute(
    path:        collection_path,
    displayname: col[:displayname],
    description: col[:description],
    color:       col[:color],
    item_etags:  item_etags,
  )
  token = "http://caldav.local/sync/#{ctag}"
  @sync_snapshots[token] = { collection_path => snapshot }
  token
end

#sync_changes(collection_path, token) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/async/caldav/storage/filesystem.rb', line 217

def sync_changes(collection_path, token)
  old_snapshot_entry = @sync_snapshots[token]
  if old_snapshot_entry
    old_snapshot = old_snapshot_entry[collection_path] || {}
    new_token = snapshot_sync(collection_path)
    current_items = list_items(collection_path)
    current = {}
    current_items.each { |path, data| current[path] = data[:etag] }
    changes = []
    current.each do |path, etag|
      if !old_snapshot.key?(path) || old_snapshot[path] != etag
        changes << [path, :modified]
      end
    end
    old_snapshot.each_key do |path|
      unless current.key?(path)
        changes << [path, :deleted]
      end
    end
    [new_token, changes]
  else
    nil
  end
end

#update_collection(path, props) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/async/caldav/storage/filesystem.rb', line 75

def update_collection(path, props)
  col = get_collection(path)
  if col
    if props.key?(:displayname)
      col[:displayname] = props[:displayname]
    end
    if props.key?(:description)
      col[:description] = props[:description]
    end
    if props.key?(:color)
      col[:color] = props[:color]
    end
    if props.key?(:props)
      col[:props] = (col[:props] || {}).merge(props[:props])
    end
    meta = {
      "type"        => col[:type].to_s,
      "displayname" => col[:displayname],
      "description" => col[:description],
      "color"       => col[:color],
      "props"       => col[:props] || {},
    }
    File.write(File.join(full_path(path), ".collection.json"), JSON.pretty_generate(meta))
    col
  else
    nil
  end
end