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
# 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)
  uri = URI(@expanded_uri)

  raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir"
  raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host
  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.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



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

def self.suggest_labels_for path; [] end

Instance Method Details

#draft?(id) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#each_raw_message_line(id) ⇒ Object



71
72
73
74
75
76
77
# File 'lib/sup/maildir.rb', line 71

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

#file_pathObject



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

def file_path; @dir end

#flagged?(id) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#is_source_for?(uri) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#labels?(id) ⇒ Boolean

Returns:

  • (Boolean)


172
173
174
# File 'lib/sup/maildir.rb', line 172

def labels? id
  maildir_labels id
end

#load_header(id) ⇒ Object



79
80
81
# File 'lib/sup/maildir.rb', line 79

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

#load_message(id) ⇒ Object



83
84
85
# File 'lib/sup/maildir.rb', line 83

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

#maildir_labels(id) ⇒ Object



176
177
178
179
180
181
182
183
# File 'lib/sup/maildir.rb', line 176

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)


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

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

#pollObject

XXX use less memory



110
111
112
113
114
115
116
117
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
# File 'lib/sup/maildir.rb', line 110

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.glob("#{subdir}/*").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



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

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



105
106
107
# File 'lib/sup/maildir.rb', line 105

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

#replied?(id) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#seen?(id) ⇒ Boolean

Returns:

  • (Boolean)


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

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

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



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/sup/maildir.rb', line 43

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.exists? tmp_path
      end
    end #rescue Errno...
  end #Dir.chdir

  stored
end

#supported_labels?Boolean

Returns:

  • (Boolean)


35
36
37
# File 'lib/sup/maildir.rb', line 35

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

#sync_back(id, labels) ⇒ Object



87
88
89
90
91
92
93
# File 'lib/sup/maildir.rb', line 87

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)


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

def sync_back_enabled?
  @sync_back
end

#trashed?(id) ⇒ Boolean

Returns:

  • (Boolean)


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

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)


192
193
194
# File 'lib/sup/maildir.rb', line 192

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