aiwilliams-mlist 0.0.0 → 0.1.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.
@@ -1,77 +1,148 @@
1
1
  module MList
2
2
  class MailList < ActiveRecord::Base
3
- def self.find_or_create_by_list(list)
4
- mail_list = find_or_create_by_identifier(list.list_id)
5
- mail_list.manager_list = list
3
+ set_table_name 'mlist_mail_lists'
4
+
5
+ # Provides the MailList for a given implementation of MList::List,
6
+ # connecting it to the provided email server for delivering posts.
7
+ #
8
+ def self.find_or_create_by_list(list, outgoing_server)
9
+ if list.is_a?(ActiveRecord::Base)
10
+ mail_list = find_or_create_by_manager_list_identifier_and_manager_list_type_and_manager_list_id(
11
+ list.list_id, list.class.base_class.name, list.id
12
+ )
13
+ else
14
+ mail_list = find_or_create_by_manager_list_identifier(list.list_id)
15
+ mail_list.manager_list = list
16
+ end
17
+ mail_list.outgoing_server = outgoing_server
6
18
  mail_list
7
19
  end
8
20
 
9
- has_many :messages, :dependent => :delete_all
10
- has_many :threads, :dependent => :delete_all
21
+ belongs_to :manager_list, :polymorphic => true
11
22
 
12
- attr_accessor :manager_list
13
- delegate :address, :recipients, :subscriptions,
14
- :to => :manager_list
23
+ has_many :messages, :class_name => 'MList::Message', :dependent => :delete_all
24
+ has_many :threads, :class_name => 'MList::Thread', :dependent => :delete_all
15
25
 
16
- def post(email_server, message)
17
- return unless process?(message)
18
- prepare_delivery(message)
19
- deliver(message, email_server)
20
- end
26
+ delegate :address, :label, :post_url, :to => :list
27
+
28
+ attr_accessor :outgoing_server
21
29
 
22
- def been_there?(message)
23
- message.header_string('x-beenthere') == address
30
+ # Creates a new MList::Message and delivers it to the subscribers of this
31
+ # list.
32
+ #
33
+ def post(email_or_attributes)
34
+ email = email_or_attributes
35
+ email = MList::EmailPost.new(email_or_attributes) unless email.is_a?(MList::EmailPost)
36
+ process_message messages.build(
37
+ :parent => email.reply_to_message,
38
+ :parent_identifier => email.parent_identifier,
39
+ :mail_list => self,
40
+ :subscriber => email.subscriber,
41
+ :recipients => list.recipients(email.subscriber),
42
+ :email => MList::Email.new(:tmail => email.to_tmail)
43
+ ), :search_parent => false
24
44
  end
25
45
 
26
- # http://mail.python.org/pipermail/mailman-developers/2006-April/018718.html
27
- def bounce_headers
28
- {'sender' => "mlist-#{address}",
29
- 'errors-to' => "mlist-#{address}"}
46
+ # Processes the email received by the MList::Server.
47
+ #
48
+ def process_email(email)
49
+ subscriber = list.subscriber(email.from_address)
50
+ recipients = list.recipients(subscriber)
51
+ process_message messages.build(
52
+ :mail_list => self,
53
+ :subscriber => subscriber,
54
+ :recipients => recipients,
55
+ :email => email
56
+ )
30
57
  end
31
58
 
32
- def deliver(message, email_server)
33
- transaction do
34
- email_server.deliver(message.tmail)
35
- thread = find_thread(message)
36
- thread.messages << message
37
- thread.save!
38
- end
59
+ def list
60
+ @list ||= manager_list
39
61
  end
40
62
 
41
- def find_thread(message)
42
- if message.reply?
43
- threads.find(:first,
44
- :joins => :messages,
45
- :readonly => false,
46
- :conditions => ['messages.identifier = ?', message.parent_identifier]
47
- )
63
+ def manager_list_with_dual_type=(list)
64
+ if list.is_a?(ActiveRecord::Base)
65
+ self.manager_list_without_dual_type = list
66
+ @list = list
48
67
  else
49
- threads.build
68
+ self.manager_list_without_dual_type = nil
69
+ @list = list
50
70
  end
51
71
  end
72
+ alias_method_chain :manager_list=, :dual_type
52
73
 
53
- # http://www.jamesshuggins.com/h/web1/list-email-headers.htm
54
- def list_headers
55
- headers = manager_list.list_headers
56
- headers['x-beenthere'] = address
57
- headers.update(bounce_headers)
58
- headers.delete_if {|k,v| v.nil?}
59
- end
60
-
61
- def prepare_delivery(message)
62
- prepare_list_headers(message)
63
- message.to = address
64
- message.bcc = recipients(message)
74
+ def process?(message)
75
+ !message.recipients.blank?
65
76
  end
66
77
 
67
- def prepare_list_headers(message)
68
- list_headers.each do |k,v|
69
- message.write_header(k,v)
78
+ private
79
+ # http://mail.python.org/pipermail/mailman-developers/2006-April/018718.html
80
+ def bounce_headers
81
+ {'sender' => "mlist-#{address}",
82
+ 'errors-to' => "mlist-#{address}"}
83
+ end
84
+
85
+ # http://www.jamesshuggins.com/h/web1/list-email-headers.htm
86
+ def list_headers
87
+ headers = list.list_headers
88
+ headers['x-beenthere'] = address
89
+ headers.update(bounce_headers)
90
+ headers.delete_if {|k,v| v.nil?}
91
+ end
92
+
93
+ def process_message(message, options = {})
94
+ raise MList::DoubleDeliveryError.new(message) unless message.new_record?
95
+ return message unless process?(message)
96
+
97
+ options = {
98
+ :search_parent => true,
99
+ :delivery_time => Time.now
100
+ }.merge(options)
101
+ transaction do
102
+ thread = find_thread(message, options)
103
+ thread.messages << message
104
+ prepare_delivery(message)
105
+ destinations = message.delivery.destinations
106
+ tmail = message.to_tmail
107
+ self.updated_at = thread.updated_at = options[:delivery_time]
108
+ thread.save! && save!
109
+ outgoing_server.deliver(tmail, destinations)
110
+ end
111
+ message
112
+ end
113
+
114
+ def prepare_delivery(message)
115
+ delivery = message.delivery
116
+ prepare_list_headers(delivery)
117
+ delivery.subject = list_subject(message)
118
+ delivery.to = address
119
+ delivery.bcc = message.recipients
120
+ delivery.reply_to = "#{label} <#{post_url}>"
121
+ end
122
+
123
+ def prepare_list_headers(delivery)
124
+ list_headers.each do |k,v|
125
+ if TMail::Mail::ALLOW_MULTIPLE.include?(k.downcase)
126
+ delivery.prepend_header(k,v)
127
+ else
128
+ delivery.write_header(k,v)
129
+ end
130
+ end
131
+ end
132
+
133
+ def list_subject(message)
134
+ prefix = "[#{label}]"
135
+ subject = message.subject.gsub(%r(#{Regexp.escape(prefix)}\s*), '')
136
+ subject.gsub!(%r{(re:\s*){2,}}i, 'Re: ')
137
+ "#{prefix} #{subject}"
138
+ end
139
+
140
+ def find_thread(message, options)
141
+ if options[:search_parent]
142
+ message.parent_identifier = message.email.parent_identifier(self)
143
+ message.parent = messages.find_by_identifier(message.parent_identifier)
144
+ end
145
+ message.parent ? message.parent.thread : threads.build
70
146
  end
71
- end
72
-
73
- def process?(message)
74
- !been_there?(message) && !recipients(message).blank?
75
- end
76
147
  end
77
148
  end
@@ -12,20 +12,24 @@ module MList
12
12
 
13
13
  def lists(email)
14
14
  lists = List.find_all_by_address(email.list_addresses)
15
- email.list_addresses.map { |a| lists.detect {|l| l.address == a} }
15
+ email.list_addresses.map { |a| lists.detect {|l| l.address == a} }.compact
16
16
  end
17
17
 
18
18
  class List < ActiveRecord::Base
19
19
  include ::MList::List
20
20
 
21
- has_many :subscriptions, :dependent => :delete_all
21
+ has_many :subscribers, :dependent => :delete_all
22
+
23
+ def list_id
24
+ "#{self.class.name}#{id}"
25
+ end
22
26
 
23
27
  def subscribe(address)
24
- subscriptions.find_or_create_by_address(address)
28
+ subscribers.find_or_create_by_email_address(address)
25
29
  end
26
30
  end
27
31
 
28
- class Subscription < ActiveRecord::Base
32
+ class Subscriber < ActiveRecord::Base
29
33
  belongs_to :list
30
34
  end
31
35
  end
@@ -1,107 +1,111 @@
1
1
  module MList
2
2
 
3
- # The persisted version of an email that is processed by MList::MailLists.
4
- #
5
- # The tmail object referenced by these are unique, though they may reference
6
- # the 'same' originating email.
7
- #
8
3
  class Message < ActiveRecord::Base
9
- belongs_to :mail_list
4
+ set_table_name 'mlist_messages'
10
5
 
11
- attr_writer :header_sanitizers
12
- before_save :serialize_tmail
6
+ include MList::Util::EmailHelpers
13
7
 
14
- def charset
15
- 'utf-8'
16
- end
8
+ belongs_to :email, :class_name => 'MList::Email'
9
+ belongs_to :parent, :class_name => 'MList::Message'
10
+ belongs_to :mail_list, :class_name => 'MList::MailList', :counter_cache => :messages_count
11
+ belongs_to :thread, :class_name => 'MList::Thread', :counter_cache => :messages_count
17
12
 
18
- def delete_header(name)
19
- tmail[name] = nil
20
- end
13
+ # A temporary storage of recipient subscribers, obtained from
14
+ # MList::Lists. This list is not available when a message is reloaded.
15
+ #
16
+ attr_accessor :recipients
21
17
 
22
- def from_address
23
- tmail.from.first
18
+ def delivery
19
+ @delivery ||= MList::Util::TMailBuilder.new(TMail::Mail.parse(email.source))
24
20
  end
25
21
 
26
- def parent_identifier
27
- if in_reply_to = header_string('in-reply-to')
28
- identifier = in_reply_to
29
- elsif references = read_header('references')
30
- identifier = references.ids.first
31
- else
32
- parent_message = mail_list.messages.find(:first,
33
- :conditions => ['messages.subject = ?', remove_regard(subject)],
34
- :order => 'created_at asc'
35
- )
36
- identifier = parent_message.identifier if parent_message
37
- end
38
- remove_brackets(identifier) if identifier
22
+ def email_with_capture=(email)
23
+ self.subject = email.subject
24
+ self.mailer = email.mailer
25
+ self.email_without_capture = email
39
26
  end
27
+ alias_method_chain :email=, :capture
40
28
 
41
- def read_header(name)
42
- tmail[name]
29
+ # Answers the html content of the message.
30
+ #
31
+ def html
32
+ email.html
43
33
  end
44
34
 
45
- def reply?
46
- !parent_identifier.nil?
35
+ # Answers the text content of the message.
36
+ #
37
+ def text
38
+ email.text
47
39
  end
48
40
 
49
- def write_header(name, value)
50
- tmail[name] = sanitize_header(name, value)
41
+ # Answers the text content of the message as HTML. The structure of this
42
+ # output is very simple. For examples of what it can handle, please check
43
+ # out the spec documents for MList::Util::EmailHelpers.
44
+ #
45
+ def text_html
46
+ text_to_html(text)
51
47
  end
52
48
 
53
- def tmail=(tmail)
54
- write_attribute(:identifier, remove_brackets(tmail.header_string('message-id')))
55
- write_attribute(:subject, tmail.subject)
56
- @tmail = tmail
49
+ # Answers text suitable for creating a reply message.
50
+ #
51
+ def text_for_reply
52
+ timestamp = email.date.to_s(:mlist_reply_timestamp)
53
+ "On #{timestamp}, #{email.from} wrote:\n#{text_to_quoted(text)}"
57
54
  end
58
55
 
59
- def tmail
60
- @tmail ||= TMail::Mail.parse(email_text)
56
+ # Answers text suitable for creating a reply message, converted to the
57
+ # same simple html of _text_html_.
58
+ #
59
+ def html_for_reply
60
+ text_to_html(text_for_reply)
61
61
  end
62
62
 
63
- def to=(recipients)
64
- tmail.to = sanitize_header('to', recipients)
63
+ # Answers the subject with all prefixes removed.
64
+ #
65
+ # message.subject = 'Re: [List Label] Re: The new Chrome Browser from Google'
66
+ # message.subject_for_reply => 'Re: The new Chrome Browser from Google'
67
+ #
68
+ def subject_for_reply
69
+ "Re: #{remove_regard(subject)}"
65
70
  end
66
71
 
67
- def bcc=(recipients)
68
- tmail.bcc = sanitize_header('bcc', recipients)
72
+ # Answers the subscriber from which this message comes.
73
+ #
74
+ def subscriber
75
+ @subscriber ||= begin
76
+ if subscriber_type? && subscriber_id?
77
+ subscriber_type.constantize.find(subscriber_id)
78
+ elsif subscriber_address?
79
+ MList::EmailSubscriber.new(subscriber_address)
80
+ end
81
+ end
69
82
  end
70
83
 
71
- # Provide delegation to *most* of the underlying TMail::Mail methods,
72
- # excluding those overridden by this class and the [] and []= methods. We
73
- # must maintain the ActiveRecord interface over that of the TMail::Mail
74
- # interface.
84
+ # Assigns the subscriber from which this message comes.
75
85
  #
76
- def method_missing(symbol, *args, &block) # :nodoc:
77
- if @tmail && @tmail.respond_to?(symbol) && !(symbol == :[] || symbol == :[]=)
78
- @tmail.__send__(symbol, *args, &block)
86
+ def subscriber=(subscriber)
87
+ case subscriber
88
+ when ActiveRecord::Base
89
+ @subscriber = subscriber
90
+ self.subscriber_address = subscriber.email_address
91
+ self.subscriber_type = subscriber.class.base_class.name
92
+ self.subscriber_id = subscriber.id
93
+ when MList::EmailSubscriber
94
+ @subscriber = subscriber
95
+ self.subscriber_address = subscriber.email_address
96
+ self.subscriber_type = self.subscriber_id = nil
97
+ when String
98
+ self.subscriber = MList::EmailSubscriber.new(subscriber)
79
99
  else
80
- super
100
+ @subscriber = self.subscriber_address = self.subscriber_type = self.subscriber_id = nil
81
101
  end
82
102
  end
83
103
 
84
- def sanitize_header(name, *values)
85
- header_sanitizer(name).call(charset, *values)
104
+ def to_tmail
105
+ delivery.mailer = mailer
106
+ delivery.ready_to_send
107
+ self.identifier = delivery.identifier
108
+ delivery.tmail
86
109
  end
87
-
88
- private
89
- def header_sanitizer(name)
90
- @header_sanitizers ||= Util.default_header_sanitizers
91
- @header_sanitizers[name]
92
- end
93
-
94
- def remove_brackets(string)
95
- string =~ /\A<(.*?)>\Z/ ? $1 : string
96
- end
97
-
98
- def remove_regard(string)
99
- stripped = string.strip
100
- stripped =~ /\Are:\s+(.*?)\Z/i ? $1 : stripped
101
- end
102
-
103
- def serialize_tmail
104
- write_attribute(:email_text, @tmail.to_s)
105
- end
106
110
  end
107
111
  end
@@ -8,7 +8,7 @@ module MList
8
8
  @email_server.receiver(self)
9
9
  end
10
10
 
11
- def receive(email)
11
+ def receive_email(email)
12
12
  lists = list_manager.lists(email)
13
13
  if email.bounce?
14
14
  process_bounce(lists.first, email)
@@ -17,6 +17,10 @@ module MList
17
17
  end
18
18
  end
19
19
 
20
+ def mail_list(list)
21
+ MailList.find_or_create_by_list(list, @email_server)
22
+ end
23
+
20
24
  protected
21
25
  def process_bounce(list, email)
22
26
  list.bounce(email)
@@ -24,11 +28,15 @@ module MList
24
28
 
25
29
  def process_post(lists, email)
26
30
  lists.each do |list|
31
+ next if email.been_here?(list)
27
32
  if list.subscriber?(email.from_address)
28
- mail_list = MailList.find_or_create_by_list(list)
29
- mail_list.post(email_server, MList::Message.new(:mail_list => mail_list, :tmail => email.tmail))
33
+ if list.active?
34
+ mail_list(list).process_email(email)
35
+ else
36
+ list.inactive_post(email)
37
+ end
30
38
  else
31
- list.non_subscriber_posted(email)
39
+ list.non_subscriber_post(email)
32
40
  end
33
41
  end
34
42
  end