rims 0.2.1
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
- data/.gitignore +17 -0
- data/ChangeLog +379 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +566 -0
- data/Rakefile +29 -0
- data/bin/rims +11 -0
- data/lib/rims.rb +45 -0
- data/lib/rims/auth.rb +133 -0
- data/lib/rims/cksum_kvs.rb +68 -0
- data/lib/rims/cmd.rb +809 -0
- data/lib/rims/daemon.rb +338 -0
- data/lib/rims/db.rb +793 -0
- data/lib/rims/error.rb +23 -0
- data/lib/rims/gdbm_kvs.rb +76 -0
- data/lib/rims/hash_kvs.rb +66 -0
- data/lib/rims/kvs.rb +101 -0
- data/lib/rims/lock.rb +151 -0
- data/lib/rims/mail_store.rb +663 -0
- data/lib/rims/passwd.rb +251 -0
- data/lib/rims/pool.rb +88 -0
- data/lib/rims/protocol.rb +71 -0
- data/lib/rims/protocol/decoder.rb +1469 -0
- data/lib/rims/protocol/parser.rb +1114 -0
- data/lib/rims/rfc822.rb +456 -0
- data/lib/rims/server.rb +567 -0
- data/lib/rims/test.rb +391 -0
- data/lib/rims/version.rb +11 -0
- data/load_test/Rakefile +93 -0
- data/rims.gemspec +38 -0
- data/test/test_auth.rb +174 -0
- data/test/test_cksum_kvs.rb +121 -0
- data/test/test_config.rb +533 -0
- data/test/test_daemon_status_file.rb +169 -0
- data/test/test_daemon_waitpid.rb +72 -0
- data/test/test_db.rb +602 -0
- data/test/test_db_recovery.rb +732 -0
- data/test/test_error.rb +97 -0
- data/test/test_gdbm_kvs.rb +32 -0
- data/test/test_hash_kvs.rb +116 -0
- data/test/test_lock.rb +161 -0
- data/test/test_mail_store.rb +750 -0
- data/test/test_passwd.rb +203 -0
- data/test/test_protocol.rb +91 -0
- data/test/test_protocol_auth.rb +121 -0
- data/test/test_protocol_decoder.rb +6490 -0
- data/test/test_protocol_fetch.rb +994 -0
- data/test/test_protocol_request.rb +332 -0
- data/test/test_protocol_search.rb +974 -0
- data/test/test_rfc822.rb +696 -0
- metadata +174 -0
data/lib/rims/error.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module RIMS
|
4
|
+
class Error < StandardError
|
5
|
+
def self.suppress_2nd_error_at_resource_closing(logger: nil)
|
6
|
+
if ($!) then
|
7
|
+
begin
|
8
|
+
yield
|
9
|
+
rescue # not mask the first error
|
10
|
+
logger.error($!) if logger
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
else
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Local Variables:
|
21
|
+
# mode: Ruby
|
22
|
+
# indent-tabs-mode: nil
|
23
|
+
# End:
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'gdbm'
|
4
|
+
|
5
|
+
module RIMS
|
6
|
+
class GDBM_KeyValueStore < KeyValueStore
|
7
|
+
def initialize(gdbm, path)
|
8
|
+
@db = gdbm
|
9
|
+
@path = path
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def exist?(path)
|
14
|
+
gdbm_path = path + '.gdbm'
|
15
|
+
File.exist? gdbm_path
|
16
|
+
end
|
17
|
+
|
18
|
+
def open(path, *optional)
|
19
|
+
gdbm_path = path + '.gdbm'
|
20
|
+
new(GDBM.new(gdbm_path, *optional), gdbm_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def open_with_conf(name, config)
|
24
|
+
open(name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](key)
|
29
|
+
@db[key]
|
30
|
+
end
|
31
|
+
|
32
|
+
def []=(key, value)
|
33
|
+
@db[key] = value
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete(key)
|
37
|
+
@db.delete(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
def key?(key)
|
41
|
+
@db.key? key
|
42
|
+
end
|
43
|
+
|
44
|
+
def each_key
|
45
|
+
return enum_for(:each_key) unless block_given?
|
46
|
+
@db.each_key do |key|
|
47
|
+
yield(key)
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def sync
|
53
|
+
@db.sync
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def close
|
58
|
+
@db.close
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def destroy
|
63
|
+
unless (@db.closed?) then
|
64
|
+
raise "failed to destroy gdbm that isn't closed: #{@path}"
|
65
|
+
end
|
66
|
+
File.delete(@path)
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
KeyValueStore::FactoryBuilder.add_plug_in('gdbm', GDBM_KeyValueStore)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Local Variables:
|
74
|
+
# mode: Ruby
|
75
|
+
# indent-tabs-mode: nil
|
76
|
+
# End:
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module RIMS
|
4
|
+
class Hash_KeyValueStore < KeyValueStore
|
5
|
+
def initialize(hash)
|
6
|
+
@db = hash
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](key)
|
10
|
+
unless (key.is_a? String) then
|
11
|
+
raise "not a string key: #{key}"
|
12
|
+
end
|
13
|
+
@db[key.b]
|
14
|
+
end
|
15
|
+
|
16
|
+
def []=(key, value)
|
17
|
+
unless (key.is_a? String) then
|
18
|
+
raise "not a string key: #{key}"
|
19
|
+
end
|
20
|
+
unless (value.is_a? String) then
|
21
|
+
raise "not a string value: #{value}"
|
22
|
+
end
|
23
|
+
@db[key.b] = value.b
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(key)
|
27
|
+
unless (key.is_a? String) then
|
28
|
+
raise "not a string key: #{key}"
|
29
|
+
end
|
30
|
+
@db.delete(key.b)
|
31
|
+
end
|
32
|
+
|
33
|
+
def key?(key)
|
34
|
+
unless (key.is_a? String) then
|
35
|
+
raise "not a string key: #{key}"
|
36
|
+
end
|
37
|
+
@db.key? key.b
|
38
|
+
end
|
39
|
+
|
40
|
+
def each_key
|
41
|
+
return enum_for(:each_key) unless block_given?
|
42
|
+
@db.each_key do |key|
|
43
|
+
yield(key)
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def sync
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def close
|
53
|
+
@db = nil
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def destroy
|
58
|
+
self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Local Variables:
|
64
|
+
# mode: Ruby
|
65
|
+
# indent-tabs-mode: nil
|
66
|
+
# End:
|
data/lib/rims/kvs.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module RIMS
|
4
|
+
class KeyValueStore
|
5
|
+
def [](key)
|
6
|
+
raise NotImplementedError, 'abstract'
|
7
|
+
end
|
8
|
+
|
9
|
+
def []=(key, value)
|
10
|
+
raise NotImplementedError, 'abstract'
|
11
|
+
end
|
12
|
+
|
13
|
+
def delete(key)
|
14
|
+
raise NotImplementedError, 'abstract'
|
15
|
+
end
|
16
|
+
|
17
|
+
def key?(key)
|
18
|
+
raise NotImplementedError, 'abstract'
|
19
|
+
end
|
20
|
+
|
21
|
+
def each_key
|
22
|
+
raise NotImplementedError, 'abstract'
|
23
|
+
end
|
24
|
+
|
25
|
+
def each_value
|
26
|
+
return enum_for(:each_value) unless block_given?
|
27
|
+
each_key do |key|
|
28
|
+
yield(self[key])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def each_pair
|
33
|
+
return enum_for(:each_pair) unless block_given?
|
34
|
+
each_key do |key|
|
35
|
+
yield(key, self[key])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def sync
|
40
|
+
raise NotImplementedError, 'abstract'
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
raise NotImplementedError, 'abstract'
|
45
|
+
end
|
46
|
+
|
47
|
+
def destroy
|
48
|
+
raise NotImplementedError, 'abstract'
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.exist?(path)
|
52
|
+
raise NotImplementedError, 'not implemented.'
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.open_with_conf(config)
|
56
|
+
raise NotImplementedError, 'not implemented.'
|
57
|
+
end
|
58
|
+
|
59
|
+
class FactoryBuilder
|
60
|
+
PLUG_IN = {} # :nodoc:
|
61
|
+
|
62
|
+
class << self
|
63
|
+
def add_plug_in(name, klass)
|
64
|
+
PLUG_IN[name] = klass
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_plug_in(name)
|
69
|
+
PLUG_IN[name] or raise KeyError, "not found a key-value store plug-in: #{name}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize
|
74
|
+
@open = nil
|
75
|
+
@factory = proc{|name|
|
76
|
+
@open.call(name)
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_reader :factory
|
81
|
+
|
82
|
+
def open(&block) # :yields: name
|
83
|
+
@open = block
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
def use(middleware, *args, &block)
|
88
|
+
prev_factory = @factory
|
89
|
+
@factory = proc{|name|
|
90
|
+
middleware.new(prev_factory.call(name), *args, &block)
|
91
|
+
}
|
92
|
+
self
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Local Variables:
|
99
|
+
# mode: Ruby
|
100
|
+
# indent-tabs-mode: nil
|
101
|
+
# End:
|
data/lib/rims/lock.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module RIMS
|
6
|
+
class LockError < Error
|
7
|
+
end
|
8
|
+
|
9
|
+
class IllegalLockError < LockError
|
10
|
+
end
|
11
|
+
|
12
|
+
class ReadLockError < LockError
|
13
|
+
end
|
14
|
+
|
15
|
+
class ReadLockTimeoutError < ReadLockError
|
16
|
+
end
|
17
|
+
|
18
|
+
class WriteLockError < LockError
|
19
|
+
end
|
20
|
+
|
21
|
+
class WriteLockTimeoutError < LockError
|
22
|
+
end
|
23
|
+
|
24
|
+
class ReadWriteLock
|
25
|
+
DEFAULT_TIMEOUT_SECONDS = 300
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@lock = Thread::Mutex.new
|
29
|
+
@read_cond = Thread::ConditionVariable.new
|
30
|
+
@write_cond = Thread::ConditionVariable.new
|
31
|
+
@count_of_working_readers = 0
|
32
|
+
@count_of_standby_writers = 0
|
33
|
+
@prefer_to_writer = true
|
34
|
+
@writing = false
|
35
|
+
end
|
36
|
+
|
37
|
+
def read_lock(timeout_seconds=DEFAULT_TIMEOUT_SECONDS)
|
38
|
+
time_limit = Time.now + timeout_seconds
|
39
|
+
@lock.synchronize{
|
40
|
+
while (@writing || (@prefer_to_writer && @count_of_standby_writers > 0))
|
41
|
+
if (timeout_seconds > 0) then
|
42
|
+
@read_cond.wait(@lock, timeout_seconds)
|
43
|
+
else
|
44
|
+
raise ReadLockTimeoutError, 'read-lock wait timeout'
|
45
|
+
end
|
46
|
+
timeout_seconds = time_limit - Time.now
|
47
|
+
end
|
48
|
+
@count_of_working_readers += 1
|
49
|
+
}
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_unlock
|
54
|
+
@lock.synchronize{
|
55
|
+
@count_of_working_readers -= 1
|
56
|
+
@count_of_working_readers >= 0 or raise IllegalLockError, 'illegal read lock pattern: lock/unlock/unlock'
|
57
|
+
@prefer_to_writer = true
|
58
|
+
if (@count_of_standby_writers > 0) then
|
59
|
+
@write_cond.signal
|
60
|
+
end
|
61
|
+
}
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def read_synchronize(timeout_seconds=DEFAULT_TIMEOUT_SECONDS)
|
66
|
+
read_lock(timeout_seconds)
|
67
|
+
begin
|
68
|
+
yield
|
69
|
+
ensure
|
70
|
+
read_unlock
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def write_lock(timeout_seconds=DEFAULT_TIMEOUT_SECONDS)
|
75
|
+
time_limit = Time.now + timeout_seconds
|
76
|
+
@lock.synchronize{
|
77
|
+
@count_of_standby_writers += 1
|
78
|
+
begin
|
79
|
+
while (@writing || @count_of_working_readers > 0)
|
80
|
+
if (timeout_seconds > 0) then
|
81
|
+
@write_cond.wait(@lock, timeout_seconds)
|
82
|
+
else
|
83
|
+
raise WriteLockTimeoutError, 'write-lock wait timeout'
|
84
|
+
end
|
85
|
+
timeout_seconds = time_limit - Time.now
|
86
|
+
end
|
87
|
+
@writing = true
|
88
|
+
ensure
|
89
|
+
@count_of_standby_writers -= 1
|
90
|
+
end
|
91
|
+
}
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def write_unlock
|
96
|
+
@lock.synchronize{
|
97
|
+
@writing or raise IllegalLockError, 'illegal write lock pattern: lock/unlock/unlock'
|
98
|
+
@writing = false
|
99
|
+
@prefer_to_writer = false
|
100
|
+
@read_cond.broadcast
|
101
|
+
if (@count_of_standby_writers > 0) then
|
102
|
+
@write_cond.signal
|
103
|
+
end
|
104
|
+
}
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def write_synchronize(timeout_seconds=DEFAULT_TIMEOUT_SECONDS)
|
109
|
+
write_lock(timeout_seconds)
|
110
|
+
begin
|
111
|
+
yield
|
112
|
+
ensure
|
113
|
+
write_unlock
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# compatible for Thread::Mutex
|
118
|
+
alias synchronize write_synchronize
|
119
|
+
|
120
|
+
def self.write_lock_timeout_detach(first_timeout_seconds, detached_timeout_seconds, logger: Logger.new(STDOUT)) # yields: timeout_seconds
|
121
|
+
begin
|
122
|
+
logger.debug('ready to detach write-lock timeout.')
|
123
|
+
yield(first_timeout_seconds)
|
124
|
+
logger.debug('not detached write-lock timeout.')
|
125
|
+
nil
|
126
|
+
rescue WriteLockTimeoutError
|
127
|
+
logger.warn($!)
|
128
|
+
Thread.new{
|
129
|
+
begin
|
130
|
+
logger.warn('detached write-lock timeout.')
|
131
|
+
yield(detached_timeout_seconds)
|
132
|
+
logger.info('detached write-lock timeout thread is completed.')
|
133
|
+
nil
|
134
|
+
rescue WriteLockTimeoutError
|
135
|
+
logger.warn($!)
|
136
|
+
retry
|
137
|
+
rescue
|
138
|
+
logger.error('unexpected error at a detached thread and give up to retry write-lock timeout error.')
|
139
|
+
logger.error($!)
|
140
|
+
$!
|
141
|
+
end
|
142
|
+
}
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Local Variables:
|
149
|
+
# mode: Ruby
|
150
|
+
# indent-tabs-mode: nil
|
151
|
+
# End:
|
@@ -0,0 +1,663 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'logger'
|
5
|
+
require 'set'
|
6
|
+
require 'thread'
|
7
|
+
|
8
|
+
module RIMS
|
9
|
+
class MailStore
|
10
|
+
MSG_FLAG_NAMES = %w[ answered flagged deleted seen draft recent ].each{|n| n.freeze }.freeze
|
11
|
+
|
12
|
+
def initialize(meta_db, msg_db, &mbox_db_factory) # :yields: mbox_id
|
13
|
+
@meta_db = meta_db
|
14
|
+
@msg_db = msg_db
|
15
|
+
@mbox_db_factory = mbox_db_factory
|
16
|
+
|
17
|
+
@mbox_db = {}
|
18
|
+
@meta_db.each_mbox_id do |mbox_id|
|
19
|
+
@mbox_db[mbox_id] = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
if (@meta_db.dirty?) then
|
23
|
+
@abort_transaction = true
|
24
|
+
else
|
25
|
+
@abort_transaction = false
|
26
|
+
@meta_db.dirty = true
|
27
|
+
end
|
28
|
+
|
29
|
+
@server_response_queue_pool = RIMS::ObjectPool.new{|object_pool, mbox_id, object_lock|
|
30
|
+
MailboxServerResponseQueueBundleHolder.new(object_pool, mbox_id, object_lock)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_mbox_db(mbox_id)
|
35
|
+
if (@mbox_db.key? mbox_id) then
|
36
|
+
@mbox_db[mbox_id] ||= @mbox_db_factory.call(mbox_id)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
private :get_mbox_db
|
40
|
+
|
41
|
+
def abort_transaction?
|
42
|
+
@abort_transaction
|
43
|
+
end
|
44
|
+
|
45
|
+
def transaction
|
46
|
+
if (@abort_transaction) then
|
47
|
+
raise 'abort transaction.'
|
48
|
+
end
|
49
|
+
|
50
|
+
transaction_completed = false
|
51
|
+
begin
|
52
|
+
return_value = yield
|
53
|
+
transaction_completed = true
|
54
|
+
ensure
|
55
|
+
@abort_transaction = true unless transaction_completed
|
56
|
+
end
|
57
|
+
|
58
|
+
return_value
|
59
|
+
end
|
60
|
+
|
61
|
+
def recovery_data(logger: Logger.new(STDOUT))
|
62
|
+
begin
|
63
|
+
logger.info('test read all: meta DB')
|
64
|
+
@meta_db.test_read_all do |error|
|
65
|
+
logger.error("read fail: #{error}")
|
66
|
+
end
|
67
|
+
logger.info('test read all: msg DB')
|
68
|
+
@msg_db.test_read_all do |error|
|
69
|
+
logger.error("read fail: #{error}")
|
70
|
+
end
|
71
|
+
@mbox_db.each_key do |mbox_id|
|
72
|
+
logger.info("test_read_all: mailbox DB #{mbox_id}")
|
73
|
+
get_mbox_db(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
74
|
+
@mbox_db[mbox_id].test_read_all do |error|
|
75
|
+
logger.error("read fail: #{error}")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
@meta_db.recovery_start
|
80
|
+
@meta_db.recovery_phase1_msg_scan(@msg_db, logger: logger)
|
81
|
+
@meta_db.recovery_phase2_msg_scan(@msg_db, logger: logger)
|
82
|
+
@meta_db.recovery_phase3_mbox_scan(logger: logger)
|
83
|
+
@meta_db.recovery_phase4_mbox_scan(logger: logger)
|
84
|
+
@meta_db.recovery_phase5_mbox_repair(logger: logger) {|mbox_id|
|
85
|
+
if (@mbox_db.key? mbox_id) then
|
86
|
+
raise "not a lost mailbox: #{mbox_id}"
|
87
|
+
else
|
88
|
+
@mbox_db[mbox_id] = nil
|
89
|
+
get_mbox_db(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
90
|
+
end
|
91
|
+
}
|
92
|
+
@meta_db.recovery_phase6_msg_scan(@mbox_db, logger: logger)
|
93
|
+
@meta_db.recovery_phase7_mbox_msg_scan(@mbox_db, MSG_FLAG_NAMES, logger: logger)
|
94
|
+
@meta_db.recovery_phase8_lost_found(@mbox_db, logger: logger)
|
95
|
+
@meta_db.recovery_end
|
96
|
+
ensure
|
97
|
+
@abort_transaction = ! $!.nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
def close
|
104
|
+
@mbox_db.each_value do |db|
|
105
|
+
db.close if db
|
106
|
+
end
|
107
|
+
@msg_db.close
|
108
|
+
@meta_db.dirty = false unless @abort_transaction
|
109
|
+
@meta_db.close
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
def sync
|
114
|
+
transaction{
|
115
|
+
@msg_db.sync
|
116
|
+
@mbox_db.each_value do |db|
|
117
|
+
db.sync if db
|
118
|
+
end
|
119
|
+
@meta_db.sync
|
120
|
+
self
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def cnum
|
125
|
+
@meta_db.cnum
|
126
|
+
end
|
127
|
+
|
128
|
+
def uid(mbox_id)
|
129
|
+
@meta_db.mbox_uid(mbox_id)
|
130
|
+
end
|
131
|
+
|
132
|
+
def uidvalidity
|
133
|
+
@meta_db.uidvalidity
|
134
|
+
end
|
135
|
+
|
136
|
+
def add_mbox(name)
|
137
|
+
transaction{
|
138
|
+
name = 'INBOX' if (name =~ /\AINBOX\z/i)
|
139
|
+
name = name.b
|
140
|
+
|
141
|
+
mbox_id = @meta_db.add_mbox(name)
|
142
|
+
@mbox_db[mbox_id] = nil
|
143
|
+
|
144
|
+
@meta_db.cnum_succ!
|
145
|
+
|
146
|
+
mbox_id
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
def del_mbox(mbox_id)
|
151
|
+
transaction{
|
152
|
+
mbox_name = @meta_db.mbox_name(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
153
|
+
|
154
|
+
get_mbox_db(mbox_id)
|
155
|
+
mbox_db = @mbox_db.delete(mbox_id)
|
156
|
+
mbox_db.each_msg_uid do |uid|
|
157
|
+
msg_id = mbox_db.msg_id(uid)
|
158
|
+
del_msg(msg_id, mbox_id, uid)
|
159
|
+
end
|
160
|
+
mbox_db.close
|
161
|
+
mbox_db.destroy
|
162
|
+
|
163
|
+
for name in MSG_FLAG_NAMES
|
164
|
+
@meta_db.clear_mbox_flag_num(mbox_id, name)
|
165
|
+
end
|
166
|
+
@meta_db.del_mbox(mbox_id) or raise 'internal error.'
|
167
|
+
|
168
|
+
@meta_db.cnum_succ!
|
169
|
+
|
170
|
+
mbox_name
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
def rename_mbox(mbox_id, new_name)
|
175
|
+
transaction{
|
176
|
+
old_name = @meta_db.mbox_name(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
177
|
+
old_name = old_name.dup.force_encoding('utf-8')
|
178
|
+
|
179
|
+
new_name = 'INBOX' if (new_name =~ /\AINBOX\z/i)
|
180
|
+
@meta_db.rename_mbox(mbox_id, new_name.b)
|
181
|
+
|
182
|
+
@meta_db.cnum_succ!
|
183
|
+
|
184
|
+
old_name
|
185
|
+
}
|
186
|
+
end
|
187
|
+
|
188
|
+
def mbox_name(mbox_id)
|
189
|
+
if (name = @meta_db.mbox_name(mbox_id)) then
|
190
|
+
name.dup.force_encoding('utf-8')
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def mbox_id(mbox_name)
|
195
|
+
mbox_name = 'INBOX' if (mbox_name =~ /\AINBOX\z/i)
|
196
|
+
@meta_db.mbox_id(mbox_name.b)
|
197
|
+
end
|
198
|
+
|
199
|
+
def each_mbox_id
|
200
|
+
return enum_for(:each_mbox_id) unless block_given?
|
201
|
+
@meta_db.each_mbox_id do |mbox_id|
|
202
|
+
yield(mbox_id)
|
203
|
+
end
|
204
|
+
self
|
205
|
+
end
|
206
|
+
|
207
|
+
def mbox_msg_num(mbox_id)
|
208
|
+
@meta_db.mbox_msg_num(mbox_id)
|
209
|
+
end
|
210
|
+
|
211
|
+
def mbox_flag_num(mbox_id, flag_name)
|
212
|
+
if (MSG_FLAG_NAMES.include? flag_name) then
|
213
|
+
@meta_db.mbox_flag_num(mbox_id, flag_name)
|
214
|
+
else
|
215
|
+
raise "unknown flag name: #{name}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def add_msg(mbox_id, msg_text, msg_date=Time.now)
|
220
|
+
transaction{
|
221
|
+
mbox_db = get_mbox_db(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
222
|
+
|
223
|
+
msg_id = @meta_db.msg_id_succ!
|
224
|
+
@msg_db.add_msg(msg_id, msg_text)
|
225
|
+
@meta_db.set_msg_date(msg_id, msg_date)
|
226
|
+
@meta_db.set_msg_flag(msg_id, 'recent', true)
|
227
|
+
|
228
|
+
uid = @meta_db.add_msg_mbox_uid(msg_id, mbox_id)
|
229
|
+
mbox_db.add_msg(uid, msg_id)
|
230
|
+
|
231
|
+
@meta_db.cnum_succ!
|
232
|
+
|
233
|
+
uid
|
234
|
+
}
|
235
|
+
end
|
236
|
+
|
237
|
+
def del_msg(msg_id, mbox_id, uid)
|
238
|
+
mbox_uid_map = @meta_db.del_msg_mbox_uid(msg_id, mbox_id, uid)
|
239
|
+
if (mbox_uid_map.empty?) then
|
240
|
+
@meta_db.clear_msg_date(msg_id)
|
241
|
+
@meta_db.clear_msg_flag(msg_id)
|
242
|
+
@meta_db.clear_msg_mbox_uid_mapping(msg_id)
|
243
|
+
@msg_db.del_msg(msg_id)
|
244
|
+
end
|
245
|
+
@meta_db.mbox_flag_num_decrement(mbox_id, 'deleted')
|
246
|
+
nil
|
247
|
+
end
|
248
|
+
private :del_msg
|
249
|
+
|
250
|
+
def copy_msg(src_uid, src_mbox_id, dst_mbox_id)
|
251
|
+
transaction{
|
252
|
+
src_mbox_db = get_mbox_db(src_mbox_id) or raise "not found a source mailbox: #{src_mbox_id}"
|
253
|
+
dst_mbox_db = get_mbox_db(dst_mbox_id) or raise "not found a destination mailbox: #{dst_mbox_id}"
|
254
|
+
|
255
|
+
msg_id = src_mbox_db.msg_id(src_uid) or raise "not found a message: #{src_mbox_id},#{src_uid}"
|
256
|
+
dst_uid = @meta_db.add_msg_mbox_uid(msg_id, dst_mbox_id)
|
257
|
+
dst_mbox_db.add_msg(dst_uid, msg_id)
|
258
|
+
|
259
|
+
@meta_db.cnum_succ!
|
260
|
+
|
261
|
+
dst_uid
|
262
|
+
}
|
263
|
+
end
|
264
|
+
|
265
|
+
def msg_exist?(mbox_id, uid)
|
266
|
+
mbox_db = get_mbox_db(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
267
|
+
mbox_db.msg_exist? uid
|
268
|
+
end
|
269
|
+
|
270
|
+
def msg_text(mbox_id, uid)
|
271
|
+
mbox_db = get_mbox_db(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
272
|
+
msg_id = mbox_db.msg_id(uid) or raise "not found a message: #{mbox_id},#{uid}"
|
273
|
+
@msg_db.msg_text(msg_id)
|
274
|
+
end
|
275
|
+
|
276
|
+
def msg_date(mbox_id, uid)
|
277
|
+
mbox_db = get_mbox_db(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
278
|
+
msg_id = mbox_db.msg_id(uid) or raise "not found a message: #{mbox_id},#{uid}"
|
279
|
+
@meta_db.msg_date(msg_id)
|
280
|
+
end
|
281
|
+
|
282
|
+
def msg_flag(mbox_id, uid, flag_name)
|
283
|
+
mbox_db = get_mbox_db(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
284
|
+
|
285
|
+
if ((MSG_FLAG_NAMES - %w[ deleted ]).include? flag_name) then
|
286
|
+
msg_id = mbox_db.msg_id(uid) or raise "not found a message: #{mbox_id},#{uid}"
|
287
|
+
@meta_db.msg_flag(msg_id, flag_name)
|
288
|
+
elsif (flag_name == 'deleted') then
|
289
|
+
mbox_db.msg_flag_deleted(uid)
|
290
|
+
else
|
291
|
+
raise "unknown flag name: #{flag_name}"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def set_msg_flag(mbox_id, uid, flag_name, flag_value)
|
296
|
+
transaction{
|
297
|
+
mbox_db = get_mbox_db(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
298
|
+
|
299
|
+
if ((MSG_FLAG_NAMES - %w[ deleted ]).include? flag_name) then
|
300
|
+
msg_id = mbox_db.msg_id(uid) or raise "not found a message: #{mbox_id},#{uid}"
|
301
|
+
@meta_db.set_msg_flag(msg_id, flag_name, flag_value)
|
302
|
+
elsif (flag_name == 'deleted') then
|
303
|
+
prev_deleted = mbox_db.msg_flag_deleted(uid)
|
304
|
+
mbox_db.set_msg_flag_deleted(uid, flag_value)
|
305
|
+
if (! prev_deleted && flag_value) then
|
306
|
+
@meta_db.mbox_flag_num_increment(mbox_id, 'deleted')
|
307
|
+
elsif (prev_deleted && ! flag_value) then
|
308
|
+
@meta_db.mbox_flag_num_decrement(mbox_id, 'deleted')
|
309
|
+
end
|
310
|
+
else
|
311
|
+
raise "unknown flag name: #{flag_name}"
|
312
|
+
end
|
313
|
+
|
314
|
+
@meta_db.cnum_succ!
|
315
|
+
|
316
|
+
self
|
317
|
+
}
|
318
|
+
end
|
319
|
+
|
320
|
+
def each_msg_uid(mbox_id)
|
321
|
+
mbox_db = get_mbox_db(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
322
|
+
return enum_for(:each_msg_uid, mbox_id) unless block_given?
|
323
|
+
mbox_db.each_msg_uid do |uid|
|
324
|
+
yield(uid)
|
325
|
+
end
|
326
|
+
self
|
327
|
+
end
|
328
|
+
|
329
|
+
def expunge_mbox(mbox_id)
|
330
|
+
transaction{
|
331
|
+
mbox_db = get_mbox_db(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
332
|
+
|
333
|
+
uid_list = mbox_db.each_msg_uid.find_all{|uid| mbox_db.msg_flag_deleted(uid) }
|
334
|
+
msg_id_list = uid_list.map{|uid| mbox_db.msg_id(uid) }
|
335
|
+
|
336
|
+
uid_list.zip(msg_id_list) do |uid, msg_id|
|
337
|
+
mbox_db.expunge_msg(uid)
|
338
|
+
del_msg(msg_id, mbox_id, uid)
|
339
|
+
yield(uid) if block_given?
|
340
|
+
end
|
341
|
+
|
342
|
+
@meta_db.cnum_succ!
|
343
|
+
|
344
|
+
self
|
345
|
+
}
|
346
|
+
end
|
347
|
+
|
348
|
+
def select_mbox(mbox_id)
|
349
|
+
@meta_db.mbox_name(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
350
|
+
MailFolder.new(mbox_id, self).attach_server_response_queue(@server_response_queue_pool)
|
351
|
+
end
|
352
|
+
|
353
|
+
def examine_mbox(mbox_id)
|
354
|
+
@meta_db.mbox_name(mbox_id) or raise "not found a mailbox: #{mbox_id}."
|
355
|
+
MailFolder.new(mbox_id, self, read_only: true).attach_server_response_queue(@server_response_queue_pool)
|
356
|
+
end
|
357
|
+
|
358
|
+
def self.build_pool(kvs_meta_open, kvs_text_open)
|
359
|
+
RIMS::ObjectPool.new{|object_pool, unique_user_id, object_lock|
|
360
|
+
RIMS::MailStoreHolder.build(object_pool, unique_user_id, object_lock, kvs_meta_open, kvs_text_open)
|
361
|
+
}
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
class MailFolder
|
366
|
+
MessageStruct = Struct.new(:uid, :num)
|
367
|
+
|
368
|
+
def initialize(mbox_id, mail_store, read_only: false)
|
369
|
+
@mbox_id = mbox_id
|
370
|
+
@mail_store = mail_store
|
371
|
+
@read_only = read_only
|
372
|
+
@mail_folder_key = object_id
|
373
|
+
|
374
|
+
# late loding
|
375
|
+
@cnum = nil
|
376
|
+
@msg_list = nil
|
377
|
+
@uid_map = nil
|
378
|
+
end
|
379
|
+
|
380
|
+
def attach_server_response_queue(server_response_queue_pool)
|
381
|
+
@server_response_queue_bundle = server_response_queue_pool.get(@mbox_id)
|
382
|
+
@server_response_queue = @server_response_queue_bundle.attach_queue(@mail_folder_key)
|
383
|
+
self
|
384
|
+
end
|
385
|
+
|
386
|
+
def server_response_multicast_push(server_response_message)
|
387
|
+
@server_response_queue_bundle.multicast_push(server_response_message, @mail_folder_key)
|
388
|
+
self
|
389
|
+
end
|
390
|
+
|
391
|
+
def server_response?
|
392
|
+
! @server_response_queue.empty?
|
393
|
+
end
|
394
|
+
|
395
|
+
def server_response_fetch
|
396
|
+
while (server_response?)
|
397
|
+
server_response_message = @server_response_queue.pop(true)
|
398
|
+
yield(server_response_message)
|
399
|
+
end
|
400
|
+
self
|
401
|
+
end
|
402
|
+
|
403
|
+
def server_response_idle_wait
|
404
|
+
catch(:server_response_idle_wait_interrupt) {
|
405
|
+
while (server_response_message = @server_response_queue.pop(false))
|
406
|
+
server_response_list = [ server_response_message ]
|
407
|
+
server_response_fetch{|next_response_message|
|
408
|
+
if (next_response_message) then
|
409
|
+
server_response_list.push(next_response_message)
|
410
|
+
else
|
411
|
+
yield(server_response_list)
|
412
|
+
throw(:server_response_idle_wait_interrupt)
|
413
|
+
end
|
414
|
+
}
|
415
|
+
yield(server_response_list)
|
416
|
+
end
|
417
|
+
}
|
418
|
+
self
|
419
|
+
end
|
420
|
+
|
421
|
+
def server_response_idle_interrupt
|
422
|
+
@server_response_queue.push(nil)
|
423
|
+
end
|
424
|
+
|
425
|
+
def reload
|
426
|
+
@cnum = @mail_store.cnum
|
427
|
+
|
428
|
+
msg_id_list = @mail_store.each_msg_uid(@mbox_id).to_a
|
429
|
+
msg_id_list.sort!
|
430
|
+
|
431
|
+
@msg_list = Array.new(msg_id_list.length)
|
432
|
+
@uid_map = {}
|
433
|
+
|
434
|
+
msg_id_list.each_with_index do |id, i|
|
435
|
+
num = i.succ
|
436
|
+
msg = MessageStruct.new(id, num)
|
437
|
+
@msg_list[i] = msg
|
438
|
+
@uid_map[id] = msg
|
439
|
+
end
|
440
|
+
|
441
|
+
self
|
442
|
+
end
|
443
|
+
|
444
|
+
def updated?
|
445
|
+
@mail_store.cnum != @cnum
|
446
|
+
end
|
447
|
+
|
448
|
+
attr_reader :mbox_id
|
449
|
+
|
450
|
+
def [](msg_idx)
|
451
|
+
@msg_list[msg_idx]
|
452
|
+
end
|
453
|
+
|
454
|
+
def each_msg
|
455
|
+
return enum_for(:each_msg) unless block_given?
|
456
|
+
for msg in @msg_list
|
457
|
+
yield(msg)
|
458
|
+
end
|
459
|
+
self
|
460
|
+
end
|
461
|
+
|
462
|
+
def msg_find_all(msg_set, uid: false)
|
463
|
+
if (msg_set.size < @msg_list.length) then
|
464
|
+
if (uid) then
|
465
|
+
msg_set.inject([]) {|msg_list, id|
|
466
|
+
if (msg = @uid_map[id]) then
|
467
|
+
msg_list << msg
|
468
|
+
end
|
469
|
+
msg_list
|
470
|
+
}
|
471
|
+
else
|
472
|
+
msg_set.inject([]) {|msg_list, num|
|
473
|
+
if (1 <= num && num <= @msg_list.length) then
|
474
|
+
msg_list << @msg_list[num - 1]
|
475
|
+
end
|
476
|
+
msg_list
|
477
|
+
}
|
478
|
+
end
|
479
|
+
else
|
480
|
+
if (uid) then
|
481
|
+
@msg_list.find_all{|msg|
|
482
|
+
msg_set.include? msg.uid
|
483
|
+
}
|
484
|
+
else
|
485
|
+
@msg_list.find_all{|msg|
|
486
|
+
msg_set.include? msg.num
|
487
|
+
}
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
attr_reader :read_only
|
493
|
+
alias read_only? read_only
|
494
|
+
|
495
|
+
def expunge_mbox
|
496
|
+
if (@mail_store.mbox_flag_num(@mbox_id, 'deleted') > 0) then
|
497
|
+
if (block_given?) then
|
498
|
+
uid2num = {}
|
499
|
+
for msg in @msg_list
|
500
|
+
uid2num[msg.uid] = msg.num
|
501
|
+
end
|
502
|
+
|
503
|
+
msg_num_list = []
|
504
|
+
@mail_store.expunge_mbox(@mbox_id) do |uid|
|
505
|
+
num = uid2num[uid] or raise "internal error: not found a message: #{@mbox_id},#{uid}"
|
506
|
+
msg_num_list << num
|
507
|
+
end
|
508
|
+
|
509
|
+
# to prevent to decrement message sequence numbers that
|
510
|
+
# appear in a set of successive expunge responses, expunge
|
511
|
+
# command should early return an expunge response of larger
|
512
|
+
# message sequence number.
|
513
|
+
msg_num_list.sort!
|
514
|
+
msg_num_list.reverse_each do |num|
|
515
|
+
yield(num)
|
516
|
+
end
|
517
|
+
else
|
518
|
+
@mail_store.expunge_mbox(@mbox_id)
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
self
|
523
|
+
end
|
524
|
+
|
525
|
+
def close(&block)
|
526
|
+
unless (@read_only) then
|
527
|
+
expunge_mbox(&block)
|
528
|
+
@mail_store.each_msg_uid(@mbox_id) do |msg_id|
|
529
|
+
if (@mail_store.msg_flag(@mbox_id, msg_id, 'recent')) then
|
530
|
+
@mail_store.set_msg_flag(@mbox_id, msg_id, 'recent', false)
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
@mail_store = nil
|
535
|
+
|
536
|
+
@server_response_queue_bundle.detach_queue(@mail_folder_key)
|
537
|
+
@server_response_queue_bundle.return_pool
|
538
|
+
@server_response_queue_bundle = nil
|
539
|
+
|
540
|
+
self
|
541
|
+
end
|
542
|
+
|
543
|
+
def parse_msg_set(msg_set_desc, uid: false)
|
544
|
+
if (@msg_list.empty?) then
|
545
|
+
last_number = 0
|
546
|
+
else
|
547
|
+
if (uid) then
|
548
|
+
last_number = @msg_list[-1].uid
|
549
|
+
else
|
550
|
+
last_number = @msg_list[-1].num
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
self.class.parse_msg_set(msg_set_desc, last_number)
|
555
|
+
end
|
556
|
+
|
557
|
+
def self.parse_msg_seq(msg_seq_desc, last_number)
|
558
|
+
case (msg_seq_desc)
|
559
|
+
when /\A(\d+|\*)\z/
|
560
|
+
msg_seq_pair = [ $&, $& ]
|
561
|
+
when /\A(\d+|\*):(\d+|\*)\z/
|
562
|
+
msg_seq_pair = [ $1, $2 ]
|
563
|
+
else
|
564
|
+
raise MessageSetSyntaxError, "invalid message sequence format: #{msg_seq_desc}"
|
565
|
+
end
|
566
|
+
|
567
|
+
msg_seq_pair.map!{|num|
|
568
|
+
case (num)
|
569
|
+
when '*'
|
570
|
+
last_number
|
571
|
+
else
|
572
|
+
n = num.to_i
|
573
|
+
if (n < 1) then
|
574
|
+
raise MessageSetSyntaxError, "out of range of message sequence number: #{msg_seq_desc}"
|
575
|
+
end
|
576
|
+
n
|
577
|
+
end
|
578
|
+
}
|
579
|
+
|
580
|
+
Range.new(msg_seq_pair[0], msg_seq_pair[1])
|
581
|
+
end
|
582
|
+
|
583
|
+
def self.parse_msg_set(msg_set_desc, last_number)
|
584
|
+
msg_set = [].to_set
|
585
|
+
msg_set_desc.split(/,/).each do |msg_seq_desc|
|
586
|
+
msg_range = parse_msg_seq(msg_seq_desc, last_number)
|
587
|
+
msg_range.step do |n|
|
588
|
+
msg_set << n
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
msg_set
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
class MailStoreHolder < ObjectPool::ObjectHolder
|
597
|
+
extend Forwardable
|
598
|
+
|
599
|
+
def self.build(object_pool, unique_user_id, object_lock, kvs_meta_open, kvs_text_open)
|
600
|
+
kvs_build = proc{|kvs_open, db_name|
|
601
|
+
kvs_open.call(MAILBOX_DATA_STRUCTURE_VERSION, unique_user_id, db_name)
|
602
|
+
}
|
603
|
+
|
604
|
+
mail_store = MailStore.new(DB::Meta.new(kvs_build.call(kvs_meta_open, 'meta')),
|
605
|
+
DB::Message.new(kvs_build.call(kvs_text_open, 'message'))) {|mbox_id|
|
606
|
+
DB::Mailbox.new(kvs_build.call(kvs_meta_open, "mailbox_#{mbox_id}"))
|
607
|
+
}
|
608
|
+
mail_store.add_mbox('INBOX') unless mail_store.mbox_id('INBOX')
|
609
|
+
|
610
|
+
new(object_pool, unique_user_id, object_lock, mail_store)
|
611
|
+
end
|
612
|
+
|
613
|
+
def initialize(object_pool, unique_user_id, object_lock, mail_store)
|
614
|
+
super(object_pool, unique_user_id)
|
615
|
+
@object_lock = object_lock
|
616
|
+
@mail_store = mail_store
|
617
|
+
end
|
618
|
+
|
619
|
+
alias unique_user_id object_key
|
620
|
+
attr_reader :mail_store
|
621
|
+
|
622
|
+
def_delegator :@object_lock, :read_synchronize
|
623
|
+
def_delegator :@object_lock, :write_synchronize
|
624
|
+
|
625
|
+
def object_destroy
|
626
|
+
@mail_store.close
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
class MailboxServerResponseQueueBundleHolder < ObjectPool::ObjectHolder
|
631
|
+
def initialize(object_pool, mbox_id, object_lock)
|
632
|
+
super(object_pool, mbox_id)
|
633
|
+
@object_lock = object_lock
|
634
|
+
@queue_map = Hash.new{|h, k| h[k] = Thread::Queue.new }
|
635
|
+
end
|
636
|
+
|
637
|
+
alias mbox_id object_id
|
638
|
+
|
639
|
+
def attach_queue(mail_folder_key)
|
640
|
+
@object_lock.write_synchronize{ @queue_map[mail_folder_key] }
|
641
|
+
end
|
642
|
+
|
643
|
+
def detach_queue(mail_folder_key)
|
644
|
+
@object_lock.write_synchronize{ @queue_map.delete(mail_folder_key) } or raise "not found a queue at mail folder key: #{mail_folder_key}"
|
645
|
+
self
|
646
|
+
end
|
647
|
+
|
648
|
+
def multicast_push(server_response_message, this_mail_folder_key)
|
649
|
+
@object_lock.read_synchronize{
|
650
|
+
for mail_folder_key, queue in @queue_map
|
651
|
+
next if (mail_folder_key == this_mail_folder_key)
|
652
|
+
queue.push(server_response_message)
|
653
|
+
end
|
654
|
+
}
|
655
|
+
self
|
656
|
+
end
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
# Local Variables:
|
661
|
+
# mode: Ruby
|
662
|
+
# indent-tabs-mode: nil
|
663
|
+
# End:
|