bmf 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|