pollter_geist 0.0.1 → 0.0.2
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 +4 -4
- data/lib/pollter_geist.rb +1 -0
- data/lib/pollter_geist/change_listener.rb +38 -0
- data/lib/pollter_geist/imap_commands.rb +28 -1
- data/lib/pollter_geist/imap_idler.rb +30 -15
- data/lib/pollter_geist/version.rb +1 -1
- data/pollter_geist.gemspec +3 -0
- data/test/unit/pollter_geist/change_listener_test.rb +46 -0
- data/test/unit/pollter_geist/poller_test.rb +28 -2
- metadata +33 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 03fbdbda08f63a1cf041006fc4ae9c31447e078f
|
|
4
|
+
data.tar.gz: c8b3ba475deffa7328ae24ac6702115793210aa1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ebf69e6c55a0724232051b3295d60284ed078d4d7f18ae62c25da94bfecf3e4a76d4461afbaec0730bd2536aac3eb6ccf16c4729ae1b3714c54efbba79d953ed
|
|
7
|
+
data.tar.gz: c21efcbc291ac43e2e074481950e1c4a0378bb3c2dafc610df451d0b968814e1b0916f7383bc42a260c2e2f61b5d59ffc5ca9e322b5bceea25c0c680a54e8ceb
|
data/lib/pollter_geist.rb
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module PollterGeist
|
|
2
|
+
class ChangeListener
|
|
3
|
+
def initialize
|
|
4
|
+
@previous = []
|
|
5
|
+
@current = []
|
|
6
|
+
@added = []
|
|
7
|
+
@removed = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def tick current
|
|
11
|
+
@previous = @current
|
|
12
|
+
@current = current
|
|
13
|
+
|
|
14
|
+
@added = @current - @previous
|
|
15
|
+
@removed = @previous - @current
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def added
|
|
19
|
+
@added
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def added?
|
|
23
|
+
!added.empty?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def removed?
|
|
27
|
+
!removed.empty?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def changed?
|
|
31
|
+
added? || removed?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def removed
|
|
35
|
+
@removed
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -15,13 +15,15 @@ module PollterGeist
|
|
|
15
15
|
# Issues IDLE command for the imap server, it takes a list of events to listen for and returns once one of
|
|
16
16
|
# events occured or if there was a timeout.
|
|
17
17
|
#
|
|
18
|
-
# @param events [events] a list of events to listen for, EXPUNGE, RECENT
|
|
18
|
+
# @param events [events] a list of events to listen for, EXPUNGE, RECENT, EXISTS
|
|
19
19
|
# @param timeout [Numeric] a timeout, if no event has ocoured for this time
|
|
20
20
|
# @return will return an object which responds to #success?, #timeout? and #event
|
|
21
21
|
def wait_for events, timeout = 29*60
|
|
22
22
|
@idler.idle(events, timeout)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# fetches a list of message UIDs
|
|
26
|
+
# @return [Array] of message UIDs
|
|
25
27
|
def message_uids
|
|
26
28
|
@logger.debug('fetching uids')
|
|
27
29
|
# Searching for all messages where TO include @ is a workaround, since the imap#status method
|
|
@@ -31,11 +33,36 @@ module PollterGeist
|
|
|
31
33
|
result.map { |r| r.attr['UID'].to_i }
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
def remove uid
|
|
37
|
+
@logger.debug("removing #{uid}")
|
|
38
|
+
@imap.uid_store uid, "+FLAGS", [:Deleted]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def expunge
|
|
42
|
+
@logger.debug("expunge, remove all messages with Delete flag")
|
|
43
|
+
@imap.expunge
|
|
44
|
+
end
|
|
45
|
+
|
|
34
46
|
def fetch uids
|
|
35
47
|
logger.debug "fetching: #{uids}"
|
|
36
48
|
@imap.uid_fetch(uids, ['RFC822']).map { |r| r.attr['RFC822'] }
|
|
37
49
|
end
|
|
38
50
|
|
|
51
|
+
def wait_for_changes timeout = 20*60
|
|
52
|
+
logger.debug('waiting for changes in inbox')
|
|
53
|
+
change_set = ChangeListener.new
|
|
54
|
+
change_set.tick(message_uids)
|
|
55
|
+
result = wait_for(['RECENT', 'EXPUNGE'], timeout)
|
|
56
|
+
logger.debug('updating current view')
|
|
57
|
+
change_set.tick(message_uids)
|
|
58
|
+
if change_set.changed?
|
|
59
|
+
logger.debug('changes detected!')
|
|
60
|
+
yield(change_set)
|
|
61
|
+
else
|
|
62
|
+
logger.debug('no changes detected')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
39
66
|
def logger
|
|
40
67
|
@logger
|
|
41
68
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
require 'timeout'
|
|
2
|
+
require 'thread'
|
|
3
|
+
|
|
2
4
|
module PollterGeist
|
|
3
5
|
class ImapIdler
|
|
4
6
|
class IdleResult
|
|
@@ -26,21 +28,30 @@ module PollterGeist
|
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def idle listen_for = ['RECENT'], timeout = false
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
Thread.handle_interrupt(Timeout::Error => :never, Interrupt => :never) do
|
|
32
|
+
begin
|
|
33
|
+
Timeout::timeout(timeout) do
|
|
34
|
+
loop do
|
|
35
|
+
event = nil
|
|
36
|
+
Thread.handle_interrupt(Timeout::Error => :immediate, Interrupt => :on_blocking) {
|
|
37
|
+
event = do_idle
|
|
38
|
+
}
|
|
39
|
+
if listen_for.include? event
|
|
40
|
+
logger.debug("returning event #{event} to caller")
|
|
41
|
+
return IdleResult.new(true, event)
|
|
42
|
+
else
|
|
43
|
+
logger.debug "nope, we are not listening for #{event}"
|
|
44
|
+
end
|
|
37
45
|
end
|
|
38
46
|
end
|
|
47
|
+
rescue Timeout::Error
|
|
48
|
+
imap.idle_done
|
|
49
|
+
logger.debug 'idle timed out'
|
|
50
|
+
return IdleResult.new(false, 'TIMEOUT')
|
|
51
|
+
rescue Interrupt => e
|
|
52
|
+
logger.debug 'idle was interrupted'
|
|
53
|
+
raise e
|
|
39
54
|
end
|
|
40
|
-
rescue Timeout::Error
|
|
41
|
-
imap.idle_done
|
|
42
|
-
logger.debug 'idle timed out'
|
|
43
|
-
return IdleResult.new(false, 'TIMEOUT')
|
|
44
55
|
end
|
|
45
56
|
end
|
|
46
57
|
|
|
@@ -54,12 +65,15 @@ module PollterGeist
|
|
|
54
65
|
end
|
|
55
66
|
|
|
56
67
|
def do_idle
|
|
57
|
-
|
|
68
|
+
@events ||= Queue.new
|
|
69
|
+
until @events.empty?
|
|
70
|
+
return @events.pop
|
|
71
|
+
end
|
|
58
72
|
imap.idle do |message|
|
|
59
73
|
case message
|
|
60
74
|
when Net::IMAP::UntaggedResponse
|
|
61
75
|
logger.debug "event: #{message.name}, data: #{message.data}"
|
|
62
|
-
|
|
76
|
+
@events.push(message.name)
|
|
63
77
|
imap.idle_done
|
|
64
78
|
when Net::IMAP::ContinuationRequest
|
|
65
79
|
logger.debug 'IDLE accepted by the server'
|
|
@@ -67,7 +81,8 @@ module PollterGeist
|
|
|
67
81
|
logger.warn "Unhandled IDLE response: #{message.class}, #{message.inspect}"
|
|
68
82
|
end
|
|
69
83
|
end
|
|
70
|
-
|
|
84
|
+
logger.debug 'returning from idle'
|
|
85
|
+
@events.pop
|
|
71
86
|
end
|
|
72
87
|
end
|
|
73
88
|
end
|
data/pollter_geist.gemspec
CHANGED
|
@@ -18,6 +18,9 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
19
|
spec.require_paths = ["lib"]
|
|
20
20
|
|
|
21
|
+
spec.add_dependency('mail')
|
|
22
|
+
spec.add_dependency('virtus')
|
|
23
|
+
|
|
21
24
|
spec.add_development_dependency "bundler", "~> 1.3"
|
|
22
25
|
spec.add_development_dependency "minitest", "~> 5.0.6"
|
|
23
26
|
spec.add_development_dependency "rake"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
describe PollterGeist::ChangeListener do
|
|
4
|
+
describe '#new' do
|
|
5
|
+
it 'should allow empty' do
|
|
6
|
+
PollterGeist::ChangeListener.new
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe '#added' do
|
|
11
|
+
it 'should show added messages' do
|
|
12
|
+
cl = PollterGeist::ChangeListener.new
|
|
13
|
+
cl.added.must_be_kind_of Array
|
|
14
|
+
cl.added.must_be_empty
|
|
15
|
+
|
|
16
|
+
cl.tick([1, 2, 3])
|
|
17
|
+
cl.added.wont_be_empty
|
|
18
|
+
cl.added.must_include 1
|
|
19
|
+
cl.added.must_include 2
|
|
20
|
+
cl.added.must_include 3
|
|
21
|
+
cl.tick([1, 4])
|
|
22
|
+
cl.added.size.must_equal 1
|
|
23
|
+
cl.tick([])
|
|
24
|
+
cl.added.must_be_empty
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe '#removed' do
|
|
29
|
+
it 'should show removed messages' do
|
|
30
|
+
cl = PollterGeist::ChangeListener.new
|
|
31
|
+
cl.removed.must_be_kind_of Array
|
|
32
|
+
cl.removed.must_be_empty
|
|
33
|
+
|
|
34
|
+
cl.tick([1, 2, 3])
|
|
35
|
+
cl.removed.must_be_empty
|
|
36
|
+
cl.tick([1, 4])
|
|
37
|
+
cl.removed.size.must_equal 2
|
|
38
|
+
cl.removed.must_include 2
|
|
39
|
+
cl.removed.must_include 3
|
|
40
|
+
cl.tick([])
|
|
41
|
+
cl.removed.wont_be_empty
|
|
42
|
+
cl.tick([])
|
|
43
|
+
cl.removed.must_be_empty
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -3,10 +3,36 @@ require 'test_helper'
|
|
|
3
3
|
describe PollterGeist::Poller do
|
|
4
4
|
describe '#session' do
|
|
5
5
|
it 'should create a session' do
|
|
6
|
-
poller
|
|
6
|
+
poller = PollterGeist::Poller.new(CONFIG)
|
|
7
|
+
changer = PollterGeist::ChangeListener.new
|
|
7
8
|
poller.session do |imap|
|
|
8
9
|
imap.select_mailbox('INBOX')
|
|
9
|
-
imap.
|
|
10
|
+
uids = imap.message_uids
|
|
11
|
+
changer.tick(uids)
|
|
12
|
+
changer.tick(uids)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
=begin
|
|
16
|
+
1.times do
|
|
17
|
+
imap.wait_for('RECENT', 1)
|
|
18
|
+
changer.tick(imap.message_uids)
|
|
19
|
+
imap.logger.debug("+#{changer.added} -#{changer.removed}")
|
|
20
|
+
if changer.added?
|
|
21
|
+
puts imap.fetch(changer.added)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
=end
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
loop do
|
|
28
|
+
begin
|
|
29
|
+
imap.wait_for_changes(10 * 60) do |changes|
|
|
30
|
+
imap.logger.debug(changes)
|
|
31
|
+
end
|
|
32
|
+
rescue Interrupt => e
|
|
33
|
+
break
|
|
34
|
+
end
|
|
35
|
+
end
|
|
10
36
|
end
|
|
11
37
|
end
|
|
12
38
|
end
|
metadata
CHANGED
|
@@ -1,15 +1,43 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pollter_geist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kwando
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2013-07-
|
|
11
|
+
date: 2013-07-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: mail
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '>='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '>='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: virtus
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - '>='
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - '>='
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
13
41
|
- !ruby/object:Gem::Dependency
|
|
14
42
|
name: bundler
|
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -65,12 +93,14 @@ files:
|
|
|
65
93
|
- README.md
|
|
66
94
|
- Rakefile
|
|
67
95
|
- lib/pollter_geist.rb
|
|
96
|
+
- lib/pollter_geist/change_listener.rb
|
|
68
97
|
- lib/pollter_geist/imap_commands.rb
|
|
69
98
|
- lib/pollter_geist/imap_idler.rb
|
|
70
99
|
- lib/pollter_geist/poller.rb
|
|
71
100
|
- lib/pollter_geist/version.rb
|
|
72
101
|
- pollter_geist.gemspec
|
|
73
102
|
- test/test_helper.rb
|
|
103
|
+
- test/unit/pollter_geist/change_listener_test.rb
|
|
74
104
|
- test/unit/pollter_geist/poller_test.rb
|
|
75
105
|
- test/unit/pollter_geist_test.rb
|
|
76
106
|
homepage: ''
|
|
@@ -99,5 +129,6 @@ specification_version: 4
|
|
|
99
129
|
summary: A small abstraction layer for polling IMAP servers for changes.
|
|
100
130
|
test_files:
|
|
101
131
|
- test/test_helper.rb
|
|
132
|
+
- test/unit/pollter_geist/change_listener_test.rb
|
|
102
133
|
- test/unit/pollter_geist/poller_test.rb
|
|
103
134
|
- test/unit/pollter_geist_test.rb
|