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.



13
14
15
16
17
# File 'lib/async/caldav/storage/filesystem.rb', line 13

def initialize(root)
  @root = root
  @sync_snapshots = {}
  FileUtils.mkdir_p(@root)
end

Instance Method Details

#collection_exists?(path) ⇒ Boolean

Returns:

  • (Boolean)


85
86
87
# File 'lib/async/caldav/storage/filesystem.rb', line 85

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

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

— Collections —



21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/async/caldav/storage/filesystem.rb', line 21

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



41
42
43
44
45
46
47
48
49
# File 'lib/async/caldav/storage/filesystem.rb', line 41

def delete_collection(path)
  dir = full_path(path)
  if File.directory?(dir)
    FileUtils.rm_rf(dir)
    true
  else
    false
  end
end

#delete_item(path) ⇒ Object



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

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

#etag(path) ⇒ Object



159
160
161
162
# File 'lib/async/caldav/storage/filesystem.rb', line 159

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

#exists?(path) ⇒ Boolean

— General —

Returns:

  • (Boolean)


154
155
156
157
# File 'lib/async/caldav/storage/filesystem.rb', line 154

def exists?(path)
  fp = full_path(path)
  File.exist?(fp) || collection_exists?(path)
end

#get_collection(path) ⇒ Object



35
36
37
38
39
# File 'lib/async/caldav/storage/filesystem.rb', line 35

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

#get_item(path) ⇒ Object

— Items —



91
92
93
94
95
96
97
98
99
# File 'lib/async/caldav/storage/filesystem.rb', line 91

def get_item(path)
  file = full_path(path)
  return nil unless File.file?(file)

  body = File.read(file)
  content_type = guess_content_type(path)
  etag = Protocol::Caldav::ETag.compute(body)
  { body: body, content_type: content_type, etag: etag }
end

#get_multi(paths) ⇒ Object



148
149
150
# File 'lib/async/caldav/storage/filesystem.rb', line 148

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

#list_collections(parent_path) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/async/caldav/storage/filesystem.rb', line 51

def list_collections(parent_path)
  dir = full_path(parent_path)
  return [] unless File.directory?(dir)

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

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

#list_items(collection_path) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/async/caldav/storage/filesystem.rb', line 121

def list_items(collection_path)
  dir = full_path(collection_path)
  return [] unless File.directory?(dir)

  Dir.children(dir).filter_map do |name|
    next if name.start_with?(".")
    file = File.join(dir, name)
    next unless File.file?(file)

    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
end

#move_item(from_path, to_path) ⇒ Object



138
139
140
141
142
143
144
145
146
# File 'lib/async/caldav/storage/filesystem.rb', line 138

def move_item(from_path, to_path)
  src = full_path(from_path)
  dst = full_path(to_path)
  return nil unless File.file?(src)

  FileUtils.mkdir_p(File.dirname(dst))
  FileUtils.mv(src, dst)
  get_item(to_path)
end

#put_item(path, body, content_type) ⇒ Object



101
102
103
104
105
106
107
108
109
# File 'lib/async/caldav/storage/filesystem.rb', line 101

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 —



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/async/caldav/storage/filesystem.rb', line 166

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



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/async/caldav/storage/filesystem.rb', line 185

def sync_changes(collection_path, token)
  old_snapshot_entry = @sync_snapshots[token]
  return nil unless 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|
    changes << [path, :deleted] unless current.key?(path)
  end

  [new_token, changes]
end

#update_collection(path, props) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/async/caldav/storage/filesystem.rb', line 65

def update_collection(path, props)
  col = get_collection(path)
  return nil unless col

  col[:displayname] = props[:displayname] if props.key?(:displayname)
  col[:description] = props[:description] if props.key?(:description)
  col[:color] = props[:color] if props.key?(:color)
  col[:props] = (col[:props] || {}).merge(props[:props]) if props.key?(:props)

  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
end