Class: Redwood::Maildir

Inherits:
Source show all
Includes:
SerializeLabelsNicely
Defined in:
lib/sup/maildir.rb

Constant Summary collapse

MYHOSTNAME =
Socket.gethostname

Instance Attribute Summary

Attributes inherited from Source

#id, #usual

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SerializeLabelsNicely

#after_unmarshal!, #before_marshal

Methods inherited from Source

#==, #go_idle, parse_raw_email_header, #read?, #synchronize, #to_s, #try_lock, #unlock

Constructor Details

#initialize(uri, usual = true, archived = false, sync_back = true, id = nil, labels = []) ⇒ Maildir

Returns a new instance of Maildir.

Raises:

  • (ArgumentError)


12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/sup/maildir.rb', line 12

def initialize uri, usual=true, archived=false, sync_back=true, id=nil, labels=[]
  super uri, usual, archived, id
  @expanded_uri = Source.expand_filesystem_uri(uri)
  parts = /^([a-zA-Z0-9]*:(\/\/)?)(.*)/.match @expanded_uri
  if parts
    prefix = parts[1]
    @path = parts[3]
    uri = URI(prefix + Source.encode_path_for_uri(@path))
  else
    uri = URI(Source.encode_path_for_uri @path)
    @path = uri.path
  end

  raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir"
  raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" unless uri.host.nil? || uri.host.empty?
  raise ArgumentError, "maildir URI must have a path component" unless uri.path

  @sync_back = sync_back
  # sync by default if not specified
  @sync_back = true if @sync_back.nil?

  @dir = URI.decode_www_form_component uri.path
  @labels = Set.new(labels || [])
  @mutex = Mutex.new
  @ctimes = { 'cur' => Time.at(0), 'new' => Time.at(0) }
end

Class Method Details

.suggest_labels_for(path) ⇒ Object



40
# File 'lib/sup/maildir.rb', line 40

def self.suggest_labels_for path; [] end

Instance Method Details

#draft?(id) ⇒ Boolean

Returns:

  • (Boolean)


196
# File 'lib/sup/maildir.rb', line 196

def draft? id; maildir_data(id)[2].include? "D"; end

#each_raw_message_line(id) ⇒ Object



79
80
81
82
83
84
85
# File 'lib/sup/maildir.rb', line 79

def each_raw_message_line id
  with_file_for(id) do |f|
    until f.eof?
      yield f.gets
    end
  end
end

#file_pathObject



39
# File 'lib/sup/maildir.rb', line 39

def file_path; @dir end

#flagged?(id) ⇒ Boolean

Returns:

  • (Boolean)


197
# File 'lib/sup/maildir.rb', line 197

def flagged? id; maildir_data(id)[2].include? "F"; end

#is_source_for?(uri) ⇒ Boolean

Returns:

  • (Boolean)


41
# File 'lib/sup/maildir.rb', line 41

def is_source_for? uri; super || (uri == @expanded_uri); end

#labels?(id) ⇒ Boolean

Returns:

  • (Boolean)


183
184
185
# File 'lib/sup/maildir.rb', line 183

def labels? id
  maildir_labels id
end

#load_header(id) ⇒ Object



87
88
89
# File 'lib/sup/maildir.rb', line 87

def load_header id
  with_file_for(id) { |f| parse_raw_email_header f }
end

#load_message(id) ⇒ Object



91
92
93
# File 'lib/sup/maildir.rb', line 91

def load_message id
  with_file_for(id) { |f| RMail::Parser.read f }
end

#maildir_labels(id) ⇒ Object



187
188
189
190
191
192
193
194
# File 'lib/sup/maildir.rb', line 187

def maildir_labels id
  (seen?(id) ? [] : [:unread]) +
    (trashed?(id) ?  [:deleted] : []) +
    (flagged?(id) ? [:starred] : []) +
    (passed?(id) ? [:forwarded] : []) +
    (replied?(id) ? [:replied] : []) +
    (draft?(id) ? [:draft] : [])
end

#passed?(id) ⇒ Boolean

Returns:

  • (Boolean)


198
# File 'lib/sup/maildir.rb', line 198

def passed? id; maildir_data(id)[2].include? "P"; end

#pollObject

XXX use less memory



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/sup/maildir.rb', line 118

def poll
  added = []
  deleted = []
  updated = []
  @ctimes.each do |d,prev_ctime|
    subdir = File.join @dir, d
    debug "polling maildir #{subdir}"
    raise FatalSourceError, "#{subdir} not a directory" unless File.directory? subdir
    ctime = File.ctime subdir
    next if prev_ctime >= ctime
    @ctimes[d] = ctime

    old_ids = benchmark(:maildir_read_index) { Index.instance.enum_for(:each_source_info, self.id, "#{d}/").to_a }
    new_ids = benchmark(:maildir_read_dir) {
      Dir.open(subdir).select {
        |f| !File.directory? f}.map {
          |x| File.join(d,File.basename(x)) }.sort }
    added += new_ids - old_ids
    deleted += old_ids - new_ids
    debug "#{old_ids.size} in index, #{new_ids.size} in filesystem"
  end

  ## find updated mails by checking if an id is in both added and
  ## deleted arrays, meaning that its flags changed or that it has
  ## been moved, these ids need to be removed from added and deleted
  add_to_delete = del_to_delete = []
  map = Hash.new { |hash, key| hash[key] = [] }
  deleted.each do |id_del|
      map[maildir_data(id_del)[0]].push id_del
  end
  added.each do |id_add|
      map[maildir_data(id_add)[0]].each do |id_del|
        updated.push [ id_del, id_add ]
        add_to_delete.push id_add
        del_to_delete.push id_del
      end
  end
  added -= add_to_delete
  deleted -= del_to_delete
  debug "#{added.size} added, #{deleted.size} deleted, #{updated.size} updated"
  total_size = added.size+deleted.size+updated.size

  added.each_with_index do |id,i|
    yield :add,
    :info => id,
    :labels => @labels + maildir_labels(id) + [:inbox],
    :progress => i.to_f/total_size
  end

  deleted.each_with_index do |id,i|
    yield :delete,
    :info => id,
    :progress => (i.to_f+added.size)/total_size
  end

  updated.each_with_index do |id,i|
    yield :update,
    :old_info => id[0],
    :new_info => id[1],
    :labels => @labels + maildir_labels(id[1]),
    :progress => (i.to_f+added.size+deleted.size)/total_size
  end
  nil
end

#raw_header(id) ⇒ Object



103
104
105
106
107
108
109
110
111
# File 'lib/sup/maildir.rb', line 103

def raw_header id
  ret = ""
  with_file_for(id) do |f|
    until f.eof? || (l = f.gets) =~ /^$/
      ret += l
    end
  end
  ret
end

#raw_message(id) ⇒ Object



113
114
115
# File 'lib/sup/maildir.rb', line 113

def raw_message id
  with_file_for(id) { |f| f.read }
end

#replied?(id) ⇒ Boolean

Returns:

  • (Boolean)


199
# File 'lib/sup/maildir.rb', line 199

def replied? id; maildir_data(id)[2].include? "R"; end

#seen?(id) ⇒ Boolean

Returns:

  • (Boolean)


200
# File 'lib/sup/maildir.rb', line 200

def seen? id; maildir_data(id)[2].include? "S"; end

#store_message(date, from_email, &block) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/sup/maildir.rb', line 51

def store_message date, from_email, &block
  stored = false
  new_fn = new_maildir_basefn + ':2,S'
  Dir.chdir(@dir) do |d|
    tmp_path = File.join(@dir, 'tmp', new_fn)
    new_path = File.join(@dir, 'new', new_fn)
    begin
      sleep 2 if File.stat(tmp_path)

      File.stat(tmp_path)
    rescue Errno::ENOENT #this is what we want.
      begin
        File.open(tmp_path, 'wb') do |f|
          yield f #provide a writable interface for the caller
          f.fsync
        end

        File.safe_link tmp_path, new_path
        stored = true
      ensure
        File.unlink tmp_path if File.exist? tmp_path
      end
    end #rescue Errno...
  end #Dir.chdir

  stored
end

#supported_labels?Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/sup/maildir.rb', line 43

def supported_labels?
  [:draft, :starred, :forwarded, :replied, :unread, :deleted]
end

#sync_back(id, labels) ⇒ Object



95
96
97
98
99
100
101
# File 'lib/sup/maildir.rb', line 95

def sync_back id, labels
  synchronize do
    debug "syncing back maildir message #{id} with flags #{labels.to_a}"
    flags = maildir_reconcile_flags id, labels
    maildir_mark_file id, flags
  end
end

#sync_back_enabled?Boolean

Returns:

  • (Boolean)


47
48
49
# File 'lib/sup/maildir.rb', line 47

def sync_back_enabled?
  @sync_back
end

#trashed?(id) ⇒ Boolean

Returns:

  • (Boolean)


201
# File 'lib/sup/maildir.rb', line 201

def trashed? id; maildir_data(id)[2].include? "T"; end

#uriObject

remind me never to use inheritance again.



11
# File 'lib/sup/maildir.rb', line 11

yaml_properties :uri, :usual, :archived, :sync_back, :id, :labels

#valid?(id) ⇒ Boolean

Returns:

  • (Boolean)


203
204
205
# File 'lib/sup/maildir.rb', line 203

def valid? id
  File.exist? File.join(@dir, id)
end