bmf 0.2.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.
- checksums.yaml +7 -0
- checksums.yaml.gz.asc +11 -0
- data.tar.gz.asc +11 -0
- data/bin/bmf +8 -0
- data/lib/bmf.rb +70 -0
- data/lib/bmf/lib/address_store.rb +45 -0
- data/lib/bmf/lib/alert.rb +53 -0
- data/lib/bmf/lib/bmf.rb +364 -0
- data/lib/bmf/lib/folder.rb +76 -0
- data/lib/bmf/lib/message.rb +27 -0
- data/lib/bmf/lib/message_store.rb +250 -0
- data/lib/bmf/lib/settings.rb +74 -0
- data/lib/bmf/lib/thread_status.rb +98 -0
- data/lib/bmf/lib/xmlrpc_client.rb +27 -0
- data/lib/bmf/public/bitbrowser.css~ +15 -0
- data/lib/bmf/public/bitmessageforum.css +50 -0
- data/lib/bmf/public/css/bootstrap.min.css +9 -0
- data/lib/bmf/public/images/apple-touch-icon-114x114.png +0 -0
- data/lib/bmf/public/images/apple-touch-icon-144x144.png +0 -0
- data/lib/bmf/public/images/apple-touch-icon-57x57.png +0 -0
- data/lib/bmf/public/images/apple-touch-icon-72x72.png +0 -0
- data/lib/bmf/public/images/apple-touch-icon.png +0 -0
- data/lib/bmf/public/images/favicon.ico +0 -0
- data/lib/bmf/public/js/bootstrap.min.js +6 -0
- data/lib/bmf/public/js/html5.js +9 -0
- data/lib/bmf/public/js/jquery-1.10.1.min.js +6 -0
- data/lib/bmf/public/js/messagePoll.js +24 -0
- data/lib/bmf/views/addresses.haml +23 -0
- data/lib/bmf/views/compose.haml +38 -0
- data/lib/bmf/views/couldnt_reach_pybitmessage.haml +24 -0
- data/lib/bmf/views/home.haml +12 -0
- data/lib/bmf/views/https_quick_start.haml +71 -0
- data/lib/bmf/views/identities.haml +32 -0
- data/lib/bmf/views/layout.haml +128 -0
- data/lib/bmf/views/messages.haml +55 -0
- data/lib/bmf/views/settings.haml +18 -0
- data/lib/bmf/views/subscriptions.haml +40 -0
- data/lib/bmf/views/threads.haml +46 -0
- metadata +163 -0
- metadata.gz.asc +11 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative "message_store.rb"
|
2
|
+
require_relative "thread_status.rb"
|
3
|
+
|
4
|
+
class BMF::Folder
|
5
|
+
VALID_FOLDERS = %w{chans inbox sent lists}
|
6
|
+
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
def initialize folder_name
|
10
|
+
raise "Bad folder #{folder_name}" if !VALID_FOLDERS.include?(folder_name)
|
11
|
+
@name = folder_name
|
12
|
+
@messages = BMF::MessageStore.instance.send(folder_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def new_messages?
|
16
|
+
@messages.each_pair do |address, threads|
|
17
|
+
return true if BMF::ThreadStatus.instance.new_messages_for_address?(address, threads.keys)
|
18
|
+
end
|
19
|
+
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
|
23
|
+
def messages opts={}
|
24
|
+
msgs = @messages
|
25
|
+
|
26
|
+
if opts[:sort] == :new
|
27
|
+
msgs = msgs.sort{ |a,b| BMF::MessageStore.instance.address_last_updates[a[0]] <=> BMF::MessageStore.instance.address_last_updates[b[0]] }.reverse
|
28
|
+
end
|
29
|
+
|
30
|
+
msgs
|
31
|
+
end
|
32
|
+
|
33
|
+
def threads_for_address address, opts={}
|
34
|
+
threads = @messages[address]
|
35
|
+
|
36
|
+
if threads && opts[:sort] == :new
|
37
|
+
threads = threads.sort{ |a,b| BMF::MessageStore.instance.thread_last_updates[address][a[0]] <=> BMF::MessageStore.instance.thread_last_updates[address][b[0]] }.reverse
|
38
|
+
end
|
39
|
+
|
40
|
+
threads
|
41
|
+
end
|
42
|
+
|
43
|
+
def thread_messages address, thread_name, opts={}
|
44
|
+
threads = threads_for_address(address)
|
45
|
+
|
46
|
+
return nil if threads.nil?
|
47
|
+
|
48
|
+
msgs = threads[thread_name]
|
49
|
+
msgs = [] if msgs.nil?
|
50
|
+
|
51
|
+
if opts[:sort] == :old
|
52
|
+
msgs = msgs.sort { |a,b| BMF::Message.time(a) <=> BMF::Message.time(b) }
|
53
|
+
end
|
54
|
+
|
55
|
+
msgs
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete_thread address, thread
|
59
|
+
msgs = thread_messages(address, thread)
|
60
|
+
return [] if msgs.nil?
|
61
|
+
|
62
|
+
alerts = []
|
63
|
+
|
64
|
+
msgs.each do |msg|
|
65
|
+
msgid = msg['msgid']
|
66
|
+
if msg['_source'] == 'sent'
|
67
|
+
alerts << (BMF::XmlrpcClient.instance.trashSentMessage(msgid) + msgid)
|
68
|
+
else
|
69
|
+
alerts << (BMF::XmlrpcClient.instance.trashMessage(msgid) + msgid)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
alerts
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module BMF::Message
|
2
|
+
def self.time(m)
|
3
|
+
time = m['receivedTime']
|
4
|
+
time = m['lastActionTime'] if time.nil?
|
5
|
+
time.to_i
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.sent?(m)
|
9
|
+
!m['lastActionTime'].nil?
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.received?(m)
|
13
|
+
!m['receivedTime'].nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.sent_or_received m
|
17
|
+
if sent?(m)
|
18
|
+
:sent
|
19
|
+
elsif received?(m)
|
20
|
+
:received
|
21
|
+
else
|
22
|
+
raise "Don't know if #{m.inspect} was sent or received"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'base64'
|
3
|
+
require 'json'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
require_relative 'xmlrpc_client.rb'
|
7
|
+
|
8
|
+
class BMF::MessageStore
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@messages = {} # messages by msgid
|
15
|
+
@address_last_updates = {}
|
16
|
+
@thread_last_updates = {}
|
17
|
+
@new_messages = 0
|
18
|
+
# update
|
19
|
+
end
|
20
|
+
|
21
|
+
def log x
|
22
|
+
puts x
|
23
|
+
end
|
24
|
+
|
25
|
+
# attr_reader :messages, :address_last_updates, :thread_last_updates
|
26
|
+
|
27
|
+
def messages
|
28
|
+
Mutex.new.synchronize { @messages.dup.freeze }
|
29
|
+
end
|
30
|
+
|
31
|
+
def address_last_updates
|
32
|
+
Mutex.new.synchronize { @address_last_updates.dup.freeze }
|
33
|
+
end
|
34
|
+
|
35
|
+
def thread_last_updates
|
36
|
+
Mutex.new.synchronize { @thread_last_updates.dup.freeze }
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_times m
|
40
|
+
received_time = BMF::Message.time(m)
|
41
|
+
to_address = m["toAddress"]
|
42
|
+
|
43
|
+
# update channel access time
|
44
|
+
@address_last_updates[to_address] ||= 0
|
45
|
+
if @address_last_updates[to_address] < received_time
|
46
|
+
@address_last_updates[to_address] = received_time
|
47
|
+
end
|
48
|
+
|
49
|
+
subject = m["subject"]
|
50
|
+
if subject[0..3] == "Re: "
|
51
|
+
subject = subject[4..-1]
|
52
|
+
end
|
53
|
+
|
54
|
+
# update thread access time
|
55
|
+
@thread_last_updates[to_address] ||= {}
|
56
|
+
@thread_last_updates[to_address][subject] ||= 0
|
57
|
+
|
58
|
+
if @thread_last_updates[to_address][subject] < received_time
|
59
|
+
@thread_last_updates[to_address][subject] = received_time
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_message msgid, m
|
64
|
+
m["message"] = Base64.decode64(m["message"]).force_encoding("utf-8")
|
65
|
+
m["subject"] = Base64.decode64(m["subject"]).force_encoding("utf-8")
|
66
|
+
|
67
|
+
m["subject"] = " " if m["subject"] == ""
|
68
|
+
m["subject"] = "Re: " if m["subject"] == "Re: "
|
69
|
+
|
70
|
+
@messages[msgid] = m
|
71
|
+
|
72
|
+
to_address = m["toAddress"]
|
73
|
+
|
74
|
+
# Temp hack for lists
|
75
|
+
if to_address == "[Broadcast subscribers]"
|
76
|
+
hack_mailing_list_name = m["subject"][/\[[^\]]+\]/]
|
77
|
+
|
78
|
+
hack_mailing_list_name = m["fromAddress"] if hack_mailing_list_name.nil?
|
79
|
+
|
80
|
+
to_address += " " + hack_mailing_list_name
|
81
|
+
m["toAddress"] = to_address
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_full_message mailbox, msgid
|
86
|
+
msg_json = BMF::XmlrpcClient.instance.send("get#{mailbox.capitalize}MessageByID", msgid)
|
87
|
+
JSON.parse(msg_json)["#{mailbox}Message"][0]
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def process_messages new_messages, source="inbox"
|
92
|
+
processed_messages = 0
|
93
|
+
|
94
|
+
new_messages.each do |m|
|
95
|
+
msgid = m["msgid"]
|
96
|
+
|
97
|
+
@new_msgids[msgid] = true
|
98
|
+
|
99
|
+
if !@messages.has_key?(msgid)
|
100
|
+
processed_messages += 1
|
101
|
+
|
102
|
+
if m["message"] # cliend doesn't support getXMessageIds, so we already have everything
|
103
|
+
full_message = m
|
104
|
+
else
|
105
|
+
full_message = get_full_message(source, msgid)
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
full_message["_source"] = source
|
110
|
+
|
111
|
+
add_message msgid, full_message
|
112
|
+
|
113
|
+
update_times full_message
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
log "Added #{processed_messages} messages..." if processed_messages > 0
|
118
|
+
|
119
|
+
processed_messages
|
120
|
+
end
|
121
|
+
|
122
|
+
def init_gc
|
123
|
+
@new_msgids = {}
|
124
|
+
end
|
125
|
+
|
126
|
+
def do_gc
|
127
|
+
deleted_messages = 0
|
128
|
+
@messages.keys.each do |old_msgid|
|
129
|
+
if !@new_msgids[old_msgid]
|
130
|
+
deleted_messages += 1
|
131
|
+
@messages.delete(old_msgid)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
log "Deleted #{deleted_messages}..." if deleted_messages > 0
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_all_message_ids mailbox
|
139
|
+
method = "getAll#{mailbox.capitalize}MessageIds"
|
140
|
+
msgids = BMF::XmlrpcClient.instance.send(method)
|
141
|
+
if BMF::XmlrpcClient.is_error?(msgids) && msgids.downcase.include?("invalid method")
|
142
|
+
log "PyBitmessage doesn't support #{method}. Falling back to getAll#{mailbox.capitalize}Messages. Using a version of Pybitmessage that supports #{method} will dramatically increase performance"
|
143
|
+
msgids = BMF::XmlrpcClient.instance.send("getAll#{mailbox.capitalize}Messages")
|
144
|
+
JSON.parse(msgids)["#{mailbox}Messages"]
|
145
|
+
else
|
146
|
+
JSON.parse(msgids)["#{mailbox}MessageIds"]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
def update
|
152
|
+
|
153
|
+
inbox_messages = get_all_message_ids "inbox"
|
154
|
+
sent_messages = get_all_message_ids "sent"
|
155
|
+
|
156
|
+
new_messages = 0
|
157
|
+
|
158
|
+
lock = Mutex.new
|
159
|
+
lock.synchronize do
|
160
|
+
init_gc
|
161
|
+
|
162
|
+
new_messages += process_messages(inbox_messages)
|
163
|
+
process_messages(sent_messages, "sent")
|
164
|
+
|
165
|
+
do_gc
|
166
|
+
end
|
167
|
+
|
168
|
+
new_messages
|
169
|
+
end
|
170
|
+
|
171
|
+
def pop_new_message_count
|
172
|
+
new = 0
|
173
|
+
|
174
|
+
Mutex.new.synchronize do
|
175
|
+
new += @new_messages
|
176
|
+
@new_messages = 0
|
177
|
+
end
|
178
|
+
|
179
|
+
new
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def by_recipient sent_or_received=:nil
|
184
|
+
|
185
|
+
#display messages
|
186
|
+
|
187
|
+
by_recipient = {}
|
188
|
+
|
189
|
+
@messages.each do |id, m|
|
190
|
+
if sent_or_received
|
191
|
+
next if sent_or_received == :sent && !BMF::Message.sent?(m)
|
192
|
+
next if sent_or_received == :received && !BMF::Message.received?(m)
|
193
|
+
end
|
194
|
+
|
195
|
+
toAddress = m["toAddress"]
|
196
|
+
|
197
|
+
subject = m["subject"]
|
198
|
+
if subject[0..3] == "Re: "
|
199
|
+
subject = subject[4..-1]
|
200
|
+
end
|
201
|
+
|
202
|
+
by_recipient[toAddress] = {} if !by_recipient[toAddress]
|
203
|
+
by_recipient[toAddress][subject] = [] if !by_recipient[toAddress][subject]
|
204
|
+
by_recipient[m["toAddress"]][subject] << m
|
205
|
+
end
|
206
|
+
|
207
|
+
by_recipient
|
208
|
+
end
|
209
|
+
|
210
|
+
def inbox
|
211
|
+
by_recipient(:received).select do |toAddress, messages|
|
212
|
+
label = if BMF::AddressStore.instance.addresses.has_key? toAddress
|
213
|
+
BMF::AddressStore.instance.addresses[toAddress]['label']
|
214
|
+
else
|
215
|
+
""
|
216
|
+
end
|
217
|
+
not( label.include?("[chan]") || toAddress.include?("[Broadcast subscribers]"))
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def sent
|
222
|
+
by_recipient(:sent).select do |toAddress, messages|
|
223
|
+
label = if BMF::AddressStore.instance.addresses.has_key? toAddress
|
224
|
+
BMF::AddressStore.instance.addresses[toAddress]['label']
|
225
|
+
else
|
226
|
+
""
|
227
|
+
end
|
228
|
+
not( label.include?("[chan]") || toAddress.include?("[Broadcast subscribers]"))
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def lists
|
233
|
+
by_recipient.select do |toAddress, messages|
|
234
|
+
toAddress.include? "[Broadcast subscribers]"
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def chans
|
239
|
+
by_recipient.select do |toAddress, messages|
|
240
|
+
label = if BMF::AddressStore.instance.addresses.has_key? toAddress
|
241
|
+
BMF::AddressStore.instance.addresses[toAddress]['label']
|
242
|
+
else
|
243
|
+
""
|
244
|
+
end
|
245
|
+
label.include?("[chan]")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "singleton"
|
2
|
+
require 'yaml'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
class BMF::Settings
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
SETTINGS_AND_DESCRIPTIONS = {
|
9
|
+
:server_url => "Url of the BitMessage server to interface with.",
|
10
|
+
:sig => "Signature line to attach to messages.",
|
11
|
+
:default_send_address => "Default FROM address for messages.",
|
12
|
+
:server_interface => "Internet interface to listen on. Set to 0.0.0.0 to open up BMF to the network. WARNING!!! Anyone who can access your IP can read/post/delete/etc.",
|
13
|
+
:server_port => "Internet port to listen on.",
|
14
|
+
:display_sanitized_html => "Show sanitized HTML minus scripts, css, and any non-inline images.",
|
15
|
+
:sync_interval => "Frequency to sync inbox with PyBitmessage, in seconds. Default 60",
|
16
|
+
:user => "username for http basic authentication. (You should be using https in conjunction with this!)",
|
17
|
+
:password => "password for http basic authentication. (You should be using https in conjunction with this!)",
|
18
|
+
:https_server_key_file => "file for https key",
|
19
|
+
:https_server_certificate_file => "file for https certificate"
|
20
|
+
}
|
21
|
+
|
22
|
+
DEFAULT_SETTINGS = {"server_url" => 'http://bmf:bmf@localhost:8442/', "display_sanitized_html" => 'no' }
|
23
|
+
|
24
|
+
VALID_SETTINGS = SETTINGS_AND_DESCRIPTIONS.keys
|
25
|
+
|
26
|
+
SETTING_DIR = ".bitmessageforum"
|
27
|
+
SETTINGS_FILE = "settings.yml"
|
28
|
+
|
29
|
+
def self.fully_qualified_filename filename
|
30
|
+
home_dir = ENV["HOME"] || ENV["HOMEPATH"]
|
31
|
+
File.join(home_dir, SETTING_DIR, filename)
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
home_dir = ENV["HOME"] || ENV["HOMEPATH"]
|
36
|
+
|
37
|
+
setting_dir = File.join(home_dir, SETTING_DIR)
|
38
|
+
Dir.mkdir(setting_dir, 0700) if !File.directory? setting_dir
|
39
|
+
|
40
|
+
settings_filename = BMF::Settings.fully_qualified_filename(SETTINGS_FILE)
|
41
|
+
|
42
|
+
if !File.exists? (settings_filename)
|
43
|
+
puts "No existing settings. Copying defaults into place."
|
44
|
+
@settings = DEFAULT_SETTINGS
|
45
|
+
persist
|
46
|
+
end
|
47
|
+
|
48
|
+
@settings = YAML.load_file(settings_filename)
|
49
|
+
end
|
50
|
+
|
51
|
+
def update(key, value)
|
52
|
+
raise "Bad setting #{key}. Allowed settings #{VALID_SETTINGS.inspect}" if !VALID_SETTINGS.include?(key.to_sym)
|
53
|
+
@settings[key] = value
|
54
|
+
|
55
|
+
BMF::XmlrpcClient.instance.initialize_client if key.to_sym == :server_url
|
56
|
+
end
|
57
|
+
|
58
|
+
def persist
|
59
|
+
File.open(BMF::Settings.fully_qualified_filename(SETTINGS_FILE),'w',0600) do |out|
|
60
|
+
out.write(@settings.to_yaml)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(meth, *args)
|
65
|
+
if args == [] and VALID_SETTINGS.include?(meth)
|
66
|
+
ret = @settings[meth.to_s]
|
67
|
+
ret = nil if ret == ""
|
68
|
+
return ret
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'base64'
|
3
|
+
require_relative 'message_store.rb'
|
4
|
+
require_relative 'settings.rb'
|
5
|
+
|
6
|
+
class BMF::ThreadStatus
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
STASH_FILE = BMF::Settings.fully_qualified_filename("thread_status_stash")
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@thread_last_visited = {}
|
13
|
+
load_stash
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialize_stash
|
17
|
+
updates = []
|
18
|
+
@thread_last_visited.each_pair do |address, threads|
|
19
|
+
threads.each_pair do |thread_name, update_time|
|
20
|
+
updates << "#{address}:#{Base64.encode64(thread_name).gsub("\n","\\n")}:#{update_time}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
updates.join(";")
|
25
|
+
end
|
26
|
+
|
27
|
+
def deserialize_stash serialized_stash
|
28
|
+
serialized_stash.split(";").each do |stash_line|
|
29
|
+
address, thread, update_time = stash_line.split(':')
|
30
|
+
thread = Base64.decode64(thread.gsub("\\n","\n"))
|
31
|
+
update_time = update_time.to_i
|
32
|
+
thread_visited(address,thread,update_time)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_stash
|
37
|
+
if File.exists? STASH_FILE
|
38
|
+
stash = File.open(STASH_FILE).read
|
39
|
+
deserialize_stash stash
|
40
|
+
end
|
41
|
+
rescue Exception => ex # Failure is not an option!
|
42
|
+
puts "@" * 80
|
43
|
+
puts "Error loading ThreadStatus stash."
|
44
|
+
puts "Ignoring so that the app is usable."
|
45
|
+
puts "Please report the following information to the project maintainers"
|
46
|
+
puts
|
47
|
+
puts "Exception: #{ex.message}"
|
48
|
+
puts ex.backtrace.join("\n")
|
49
|
+
end
|
50
|
+
|
51
|
+
def persist
|
52
|
+
File.open(STASH_FILE,"w",0600) do |f|
|
53
|
+
f.write(serialize_stash)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def thread_last_visited(address, thread)
|
59
|
+
if @thread_last_visited[address] && @thread_last_visited[address][thread]
|
60
|
+
@thread_last_visited[address][thread]
|
61
|
+
else
|
62
|
+
0
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def thread_visited(address, thread, time)
|
67
|
+
@thread_last_visited[address] ||= {}
|
68
|
+
@thread_last_visited[address][thread] ||= 0
|
69
|
+
|
70
|
+
if time > @thread_last_visited[address][thread]
|
71
|
+
@thread_last_visited[address][thread] = time
|
72
|
+
end
|
73
|
+
|
74
|
+
persist
|
75
|
+
end
|
76
|
+
|
77
|
+
def new_messages?(address, thread)
|
78
|
+
if @thread_last_visited[address] && @thread_last_visited[address][thread]
|
79
|
+
last_visited_time = @thread_last_visited[address][thread]
|
80
|
+
else
|
81
|
+
last_visited_time = 0
|
82
|
+
end
|
83
|
+
|
84
|
+
last_message_time = BMF::MessageStore.instance.thread_last_updates[address][thread]
|
85
|
+
|
86
|
+
raise thread.inspect if last_message_time.nil?
|
87
|
+
|
88
|
+
last_visited_time < last_message_time
|
89
|
+
end
|
90
|
+
|
91
|
+
def new_messages_for_address?(address, threads)
|
92
|
+
return true if !@thread_last_visited[address] # never been updated
|
93
|
+
|
94
|
+
not threads.detect{ |thread| new_messages?(address, thread)}.nil?
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|