catfriend 0.16 → 0.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d3298ab27969cad7ca4f284d27fdfe153f83bbfb
4
+ data.tar.gz: dced1579477fff11e1acf0252908539893f6ca6f
5
+ SHA512:
6
+ metadata.gz: c16e9adb4de53431a43c83d1b681f1a9ed59e350f4f1885d9d13203b825185c644ea4177a790bfdb8bf39f7b3afd0c0ec43b574d3c7b2c1a56fde2a6c633da8c
7
+ data.tar.gz: 46f30291ee03d29ae6ecd795fc207f8b78c760dacc638c4b30e7a3161df86dd42399172201d9ef8baf676eb75473f5de0d1b30122aa280ecee09a28cb3fa64ee
data/bin/catfriend CHANGED
@@ -4,12 +4,14 @@
4
4
  # new e-mail arrives.
5
5
  #
6
6
  # Author:: James Pike (mailto:catfriend@chilon.net)
7
- # Copyright:: Copyright (c) 2011 James Pike
7
+ # Copyright:: Copyright (c) 2011-2014 James Pike
8
8
  # License:: MIT
9
+
10
+ $LOAD_PATH << 'lib' if File.exists? 'lib/catfriend'
11
+
9
12
  require 'catfriend/filetokenstack'
10
13
  require 'catfriend/imap'
11
14
  require 'catfriend/dbus'
12
- require 'net/imap'
13
15
  require 'optparse'
14
16
 
15
17
  module Catfriend
@@ -20,149 +22,149 @@ APP_NAME = "catfriend"
20
22
  stderr_bak = $stderr.dup
21
23
  $stderr.reopen '/dev/null', 'w'
22
24
  begin
23
- require 'xdg'
25
+ require 'xdg'
24
26
  rescue LoadError ; end
25
27
  $stderr = stderr_bak # restore stderr
26
28
 
27
29
  # Reads a simple configuration format and returns an array of servers.
28
30
  def self.parse_config
29
- # xdg is optional
30
- begin
31
- config_file = XDG['CONFIG'].find APP_NAME
32
- rescue NameError ; end
33
- config_file ||= "#{ENV['HOME']}/.config/#{APP_NAME}"
34
-
35
- # for location of certificate file
36
- Dir.chdir File.dirname(config_file)
37
-
38
- servers = []
39
- current = {}
40
- defaults = {}
41
-
42
- tokens = FileTokenStack.new config_file
43
- until tokens.empty?
44
- field = tokens.shift
45
-
46
- # obviously assigning it in a loop like this is slow but hey it's
47
- # only run-once config and ruby people say DRY a lot.
48
- shift_tokens = lambda do
49
- if tokens.empty?
50
- raise ConfigError, "field #{field} requires parameter"
51
- end
52
- return tokens.shift
53
- end
31
+ # xdg is optional
32
+ begin
33
+ config_file = XDG['CONFIG'].find APP_NAME
34
+ rescue NameError ; end
35
+ config_file ||= "#{ENV['HOME']}/.config/#{APP_NAME}"
36
+
37
+ # for location of certificate file
38
+ Dir.chdir File.dirname(config_file)
39
+
40
+ servers = []
41
+ current = {}
42
+ defaults = {}
43
+
44
+ tokens = FileTokenStack.new config_file
45
+ until tokens.empty?
46
+ field = tokens.shift
47
+
48
+ # obviously assigning it in a loop like this is slow but hey it's
49
+ # only run-once config and ruby people say DRY a lot.
50
+ shift_tokens = lambda do
51
+ if tokens.empty?
52
+ raise ConfigError, "field #{field} requires parameter"
53
+ end
54
+ return tokens.shift
55
+ end
54
56
 
55
- case field
56
- when "host","imap"
57
- # host is deprecated
58
- if not current.empty?
59
- servers << ImapServer.new(current)
60
- current = {}
61
- end
62
- current[:host] = shift_tokens.call
63
- when "notificationTimeout", "errorTimeout", "socketTimeout"
64
- # convert from camelCase to camel_case
65
- clean_field =
66
- field.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2.downcase}" }
67
- defaults[clean_field] = shift_tokens.call
68
- when "checkInterval"
69
- shift_tokens.call # deprecated, ignore parameter
70
- when "cert_file"
71
- cert_file = shift_tokens.call
72
- unless File.exists? cert_file
73
- raise ConfigError,
74
- "non-existant SSL certificate `#{cert_file}'" +
75
- ", search path: #{File.dirname(config_file)}/"
76
- end
77
- current[:cert_file] = cert_file
78
- when "mailbox", "id", "user", "password"
79
- current[field] = shift_tokens.call
80
- when "nossl"
81
- current[:no_ssl] = true
82
- when "work"
83
- current[:work_account] = true
84
- else
85
- raise ConfigError,
86
- "invalid config parameter '#{field}'"
87
- end
57
+ case field
58
+ when "host","imap"
59
+ # host is deprecated
60
+ if not current.empty?
61
+ servers << ImapServer.new(current)
62
+ current = {}
63
+ end
64
+ current[:host] = shift_tokens.call
65
+ when "notificationTimeout", "errorTimeout", "socketTimeout"
66
+ # convert from camelCase to camel_case
67
+ clean_field =
68
+ field.gsub(/([a-z])([A-Z])/) { "#{$1}_#{$2.downcase}" }
69
+ defaults[clean_field] = shift_tokens.call
70
+ when "checkInterval"
71
+ shift_tokens.call # deprecated, ignore parameter
72
+ when "cert_file"
73
+ cert_file = shift_tokens.call
74
+ unless File.exists? cert_file
75
+ raise ConfigError,
76
+ "non-existant SSL certificate `#{cert_file}'" +
77
+ ", search path: #{File.dirname(config_file)}/"
78
+ end
79
+ current[:cert_file] = cert_file
80
+ when "mailbox", "id", "user", "password"
81
+ current[field] = shift_tokens.call
82
+ when "nossl"
83
+ current[:no_ssl] = true
84
+ when "work"
85
+ current[:work_account] = true
86
+ else
87
+ raise ConfigError,
88
+ "invalid config parameter '#{field}'"
88
89
  end
90
+ end
89
91
 
90
- servers << ImapServer.new(current) unless current.empty?
91
- Catfriend.notification_timeout = (defaults["notification_timeout"] or 60).to_i
92
+ servers << ImapServer.new(current) unless current.empty?
93
+ Catfriend.notification_timeout = (defaults["notification_timeout"] or 60).to_i
92
94
 
93
- servers
95
+ servers
94
96
  end
95
97
 
96
98
  # Main interface to the application. Reads all servers from config then runs
97
99
  # each one in a thread. The program exits when all threads encounter an
98
100
  # unrecoverable error. Perhaps I should make it exit if any thread exits.
99
101
  def self.main args
100
- work_accounts = false
101
- foreground = false
102
- Catfriend.verbose = false
103
- done_action = false
104
- begin
105
- OptionParser.new do |opts|
106
- opts.banner = "usage: #{APP_NAME} [options]"
107
- opts.on("-f", "--foreground", "run in foreground") do
108
- foreground = true
109
- end
110
-
111
- opts.on("-w", "--work", "enable work accounts") do
112
- work_accounts = true
113
- end
114
- opts.on("-v", "--verbose", "verbose output to console") do
115
- Catfriend.verbose = true
116
- end
117
-
118
- opts.on("-s", "--stop", "shut down running server") do
119
- done_action = true
120
- dbus = DBus.new
121
- if dbus.send_shutdown
122
- puts "sent shutdown signal"
123
- else
124
- puts "could not send shutdown signal, no server running?"
125
- end
126
- end
127
- end.parse!
128
-
129
- return 0 if done_action
130
-
131
- Catfriend.verbose = false unless foreground
132
-
133
- servers = parse_config
134
- servers.reject! { |s| s.work_account } unless work_accounts
135
- raise ConfigError, "no servers to check" if servers.empty?
136
-
137
- if foreground
138
- main_loop servers
102
+ work_accounts = false
103
+ foreground = false
104
+ Catfriend.verbose = false
105
+ done_action = false
106
+ begin
107
+ OptionParser.new do |opts|
108
+ opts.banner = "usage: #{APP_NAME} [options]"
109
+ opts.on("-f", "--foreground", "run in foreground") do
110
+ foreground = true
111
+ end
112
+
113
+ opts.on("-w", "--work", "enable work accounts") do
114
+ work_accounts = true
115
+ end
116
+ opts.on("-v", "--verbose", "verbose output to console") do
117
+ Catfriend.verbose = true
118
+ end
119
+
120
+ opts.on("-s", "--stop", "shut down running server") do
121
+ done_action = true
122
+ dbus = DBus.new
123
+ if dbus.send_shutdown
124
+ puts "sent shutdown signal"
139
125
  else
140
- pid = fork do
141
- main_loop servers
142
- end
143
- Process.detach pid
126
+ puts "could not send shutdown signal, no server running?"
144
127
  end
145
- rescue ConfigError => e
146
- puts "misconfiguration: #{e.message}"
147
- rescue Interrupt
148
- servers.each { |s| s.kill }
149
- rescue => e
150
- puts "unknown error #{e.message}\n#{e.backtrace.join("\n")}"
128
+ end
129
+ end.parse!
130
+
131
+ return 0 if done_action
132
+
133
+ Catfriend.verbose = false unless foreground
134
+
135
+ servers = parse_config
136
+ servers.reject! { |s| s.work_account } unless work_accounts
137
+ raise ConfigError, "no servers to check" if servers.empty?
138
+
139
+ if foreground
140
+ main_loop servers
151
141
  else
152
- return 0
142
+ pid = fork do
143
+ main_loop servers
144
+ end
145
+ Process.detach pid
153
146
  end
154
-
155
- 1
147
+ rescue ConfigError => e
148
+ puts "misconfiguration: #{e.message}"
149
+ rescue Interrupt
150
+ servers.each { |s| s.kill }
151
+ rescue => e
152
+ puts "unknown error #{e.message}\n#{e.backtrace.join("\n")}"
153
+ else
154
+ return 0
155
+ end
156
+
157
+ 1
156
158
  end
157
159
 
158
160
  def self.main_loop servers
159
- dbus = DBus.new servers
160
- dbus.start
161
- servers.each { |s| s.start }
162
- servers.each { |s| s.join }
163
- dbus.join
161
+ dbus = DBus.new servers
162
+ dbus.start
163
+ servers.each { |s| s.start }
164
+ servers.each { |s| s.join }
165
+ dbus.join
164
166
  end
165
167
 
166
- end ########################### end module
168
+ end
167
169
 
168
170
  exit Catfriend.main ARGV
@@ -2,70 +2,70 @@ require 'catfriend/thread'
2
2
  require 'catfriend/server'
3
3
  require 'dbus'
4
4
 
5
- module Catfriend # {
5
+ module Catfriend
6
6
 
7
7
  SERVICE = "org.freedesktop.Catfriend"
8
8
  PATH = "/org/freedesktop/Catfriend"
9
9
  INTERFACE = "org.freedesktop.Catfriend.System"
10
10
 
11
11
  class DBus
12
- include Thread
12
+ include Thread
13
13
 
14
- class DBusObject < ::DBus::Object
15
- def initialize(main, servers)
16
- @main = main
17
- @servers = servers
18
- super PATH
19
- end
20
-
21
- dbus_interface INTERFACE do
22
- dbus_method :stop do
23
- Catfriend.whisper "received shutdown request"
24
- @main.quit # this must be run from within method handler
25
- @servers.each { |s| s.disconnect }
26
- end
27
- end
14
+ class DBusObject < ::DBus::Object
15
+ def initialize(main, servers)
16
+ @main = main
17
+ @servers = servers
18
+ super PATH
28
19
  end
29
20
 
30
- def initialize(servers = nil)
31
- @servers = servers
21
+ dbus_interface INTERFACE do
22
+ dbus_method :stop do
23
+ Catfriend.whisper "received shutdown request"
24
+ @main.quit # this must be run from within method handler
25
+ @servers.each { |s| s.disconnect }
26
+ end
32
27
  end
28
+ end
33
29
 
34
- def init
35
- @bus = ::DBus::SessionBus.instance unless @bus
36
- end
30
+ def initialize(servers = nil)
31
+ @servers = servers
32
+ end
37
33
 
38
- def send_shutdown
39
- init
40
- service = @bus.service(SERVICE)
41
- object = service.object(PATH)
42
- object.introspect
43
- object.default_iface = INTERFACE
44
- object.stop
45
- true
46
- rescue
47
- false
48
- end
34
+ def init
35
+ @bus = ::DBus::SessionBus.instance unless @bus
36
+ end
49
37
 
50
- def start_service
51
- object = DBusObject.new(@main, @servers)
52
- service = @bus.request_service(SERVICE)
53
- service.export object
54
- end
38
+ def send_shutdown
39
+ init
40
+ service = @bus.service(SERVICE)
41
+ object = service.object(PATH)
42
+ object.introspect
43
+ object.default_iface = INTERFACE
44
+ object.stop
45
+ true
46
+ rescue
47
+ false
48
+ end
55
49
 
56
- def run
57
- init
58
- if send_shutdown
59
- Catfriend.whisper "shut down existing catfriend"
60
- end
50
+ def start_service
51
+ object = DBusObject.new(@main, @servers)
52
+ service = @bus.request_service(SERVICE)
53
+ service.export object
54
+ end
61
55
 
62
- @main = ::DBus::Main.new
63
- start_service
64
- @main << @bus
65
- @main.run
66
- rescue => e
67
- puts "dbus unknown error #{e.message}\n#{e.backtrace.join("\n")}"
56
+ def run
57
+ init
58
+ if send_shutdown
59
+ Catfriend.whisper "shut down existing catfriend"
68
60
  end
61
+
62
+ @main = ::DBus::Main.new
63
+ start_service
64
+ @main << @bus
65
+ @main.run
66
+ rescue => e
67
+ puts "dbus unknown error #{e.message}\n#{e.backtrace.join("\n")}"
68
+ end
69
69
  end
70
70
 
71
- end # } end module
71
+ end
@@ -2,40 +2,40 @@ module Catfriend
2
2
 
3
3
  # Adapt a file to a stack of tokens.
4
4
  class FileTokenStack
5
- # Initialize the token stack with the path of the file, the given token
6
- # match and comment skipping regexs.
7
- def initialize file, token_match = /\S+/, comment_match = /#.*/
8
- @token_match = token_match
9
- @comment_match = comment_match
10
- @stream = File.new file, "r"
11
- @tokens = []
12
- get_next_tokens
13
- end
5
+ # Initialize the token stack with the path of the file, the given token
6
+ # match and comment skipping regexs.
7
+ def initialize file, token_match = /\S+/, comment_match = /#.*/
8
+ @token_match = token_match
9
+ @comment_match = comment_match
10
+ @stream = File.new file, "r"
11
+ @tokens = []
12
+ get_next_tokens
13
+ end
14
14
 
15
- def get_next_tokens
16
- # Never let the token stack get empty so that empty? can always
17
- # work ahead of time.. this wouldn't be good for a network stream as
18
- # it would block before delivering the last token.
19
- while @tokens.empty?
20
- @line = @stream.gets
21
- return unless @line
22
- @tokens = @line.sub(@comment_match, '').scan(@token_match) if @line
23
- end
15
+ def get_next_tokens
16
+ # Never let the token stack get empty so that empty? can always
17
+ # work ahead of time.. this wouldn't be good for a network stream as
18
+ # it would block before delivering the last token.
19
+ while @tokens.empty?
20
+ @line = @stream.gets
21
+ return unless @line
22
+ @tokens = @line.sub(@comment_match, '').scan(@token_match) if @line
24
23
  end
24
+ end
25
25
 
26
- # Shift the next token from the current stream position.
27
- def shift
28
- ret = @tokens.shift
29
- get_next_tokens
30
- ret
31
- end
26
+ # Shift the next token from the current stream position.
27
+ def shift
28
+ ret = @tokens.shift
29
+ get_next_tokens
30
+ ret
31
+ end
32
32
 
33
- # Report if any tokens remain
34
- def empty?
35
- @stream.eof and @tokens.empty?
36
- end
33
+ # Report if any tokens remain
34
+ def empty?
35
+ @stream.eof and @tokens.empty?
36
+ end
37
37
 
38
- private :get_next_tokens
38
+ private :get_next_tokens
39
39
  end
40
40
 
41
- end # end module
41
+ end
@@ -2,169 +2,176 @@ require 'libnotify'
2
2
  require 'catfriend/server'
3
3
  require 'catfriend/thread'
4
4
 
5
+ require 'net/imap'
6
+
5
7
  # unless I do this I get random errors from Libnotify on startup 90% of the
6
8
  # time... this could be a bug in autoload or ruby 1.9 rather than libnotify
7
9
  module Libnotify
8
- class API ; end
10
+ class API ; end
9
11
  end
10
12
 
11
13
  module Catfriend
12
14
 
15
+ require_relative 'net_imap_exchange_patch'
16
+
13
17
  # This class represents a thread capable of checking and creating
14
18
  # notifications for a single mailbox on a single IMAP server.
15
19
  class ImapServer
16
- include Thread
17
- include AccessorsFromHash
18
-
19
- # Create new IMAP server with optional full configuration hash.
20
- # If the hash is not supplied at construction a further call must be
21
- # made to #configure before #start is called to start the thread.
22
- def initialize(args = nil)
23
- configure args if args
20
+ include Thread
21
+ include AccessorsFromHash
22
+
23
+ # Create new IMAP server with optional full configuration hash.
24
+ # If the hash is not supplied at construction a further call must be
25
+ # made to #configure before #start is called to start the thread.
26
+ def initialize(args = nil)
27
+ configure args if args
28
+ end
29
+
30
+ # Configure all attributes based on hash then make sure this
31
+ # represents a total valid configuration.
32
+ def configure args
33
+ super args
34
+
35
+ if not @user
36
+ raise ConfigError, "imap user not set"
24
37
  end
25
-
26
- # Configure all attributes based on hash then make sure this
27
- # represents a total valid configuration.
28
- def configure args
29
- super args
30
-
31
- if not @user
32
- raise ConfigError, "imap user not set"
33
- end
34
- if not @host
35
- raise ConfigError, "imap host not set"
36
- end
37
- if not @password
38
- raise ConfigError, "imap password not set"
39
- end
38
+ if not @host
39
+ raise ConfigError, "imap host not set"
40
40
  end
41
-
42
- # The id is a token which represents this server when displaying
43
- # notifications and is set to the host unless over-ridden by the
44
- # configuration file
45
- def id ; @id || @host ; end
46
-
47
- # Raise an error related to this particular server.
48
- def error message
49
- # consider raising notification instead?
50
- puts "#{id}: #{message}"
41
+ if not @password
42
+ raise ConfigError, "imap password not set"
51
43
  end
52
-
53
- # ThreadMixin interface. This connects to the mailserver and then
54
- # runs #check_loop to do the e-mail checking if the connection
55
- # succeeds.
56
- def run
57
- begin
58
- @notification =
59
- Libnotify.new :body => nil,
60
- :timeout => Catfriend.notification_timeout
61
- @message_count = connect
62
- notify_message @message_count
63
- # :body => nil means summary only
64
- rescue OpenSSL::SSL::SSLError
65
- error "try providing ssl certificate"
66
- rescue Net::IMAP::NoResponseError
67
- error "no response to connect, try ssl"
68
- else
69
- loop {
70
- check_loop
71
- break if stopping?
72
- }
73
- end
44
+ end
45
+
46
+ # The id is a token which represents this server when displaying
47
+ # notifications and is set to the host unless over-ridden by the
48
+ # configuration file
49
+ def id ; @id || @host ; end
50
+
51
+ # Raise an error related to this particular server.
52
+ def error message
53
+ # consider raising notification instead?
54
+ puts "#{id}: #{message}"
55
+ end
56
+
57
+ # ThreadMixin interface. This connects to the mailserver and then
58
+ # runs #check_loop to do the e-mail checking if the connection
59
+ # succeeds.
60
+ def run
61
+ begin
62
+ @notification =
63
+ Libnotify.new :body => nil,
64
+ :timeout => Catfriend.notification_timeout
65
+ @message_count = connect
66
+ notify_message @message_count
67
+ # :body => nil means summary only
68
+ rescue OpenSSL::SSL::SSLError
69
+ error "try providing ssl certificate"
70
+ rescue Net::IMAP::NoResponseError
71
+ error "no response to connect, try ssl"
72
+ else
73
+ loop {
74
+ check_loop
75
+ break if stopping?
76
+ }
74
77
  end
75
-
76
- # Continually waits for new e-mail raising notifications when new
77
- # e-mail arrives or when error conditions happen. This methods only exits
78
- # on an unrecoverable error.
79
- def check_loop
80
- @imap.idle do |r|
81
- Catfriend.whisper "#{id}: #{r}"
82
- next if r.instance_of? Net::IMAP::ContinuationRequest
83
-
84
- if r.instance_of? Net::IMAP::UntaggedResponse
85
- case r.name
86
- when 'EXISTS'
87
- # some servers send this even when the message count
88
- # hasn't increased so suspiciously double-check
89
- if r.data != @message_count
90
- notify_message(r.data) if r.data > @message_count
91
- @message_count = r.data
92
- end
93
- when 'EXPUNGE'
94
- @message_count -= 1
95
- end
96
- end
97
- end
98
-
99
- Catfriend.whisper "idle loop over"
100
- rescue Net::IMAP::Error, IOError
101
- # reconnect and carry on
102
- reconnect unless stopping?
103
- rescue => e
104
- unless stopping?
105
- # todo: see if we have to re-open socket
106
- notify_message "#{@message_count} [error: #{e.message}]"
107
- puts e.backtrace.join "\n"
78
+ end
79
+
80
+ # Continually waits for new e-mail raising notifications when new
81
+ # e-mail arrives or when error conditions happen. This methods only exits
82
+ # on an unrecoverable error.
83
+ def check_loop
84
+ @imap.idle do |r|
85
+ Catfriend.whisper "#{id}: #{r}"
86
+ next if r.instance_of? Net::IMAP::ContinuationRequest
87
+
88
+ if r.instance_of? Net::IMAP::UntaggedResponse
89
+ case r.name
90
+ when 'EXISTS', 'EXPUNGE'
91
+ @imap.idle_done
108
92
  end
93
+ end
109
94
  end
110
95
 
111
- def notify_message message
112
- @notification.update :summary => "#{id}: #{message}"
113
- Catfriend.whisper @notification.summary
96
+ Catfriend.whisper "idle loop over"
97
+ count = get_unseen_count
98
+ if count != @message_count
99
+ notify_message(count) if count > @message_count
100
+ @message_count = count
114
101
  end
115
-
116
- def stopping?
117
- stopped? or @stopping
102
+ rescue Net::IMAP::Error, IOError
103
+ # reconnect and carry on
104
+ reconnect unless stopping?
105
+ rescue => e
106
+ unless stopping?
107
+ # todo: see if we have to re-open socket
108
+ notify_message "#{@message_count} [error: #{e.message}]"
109
+ puts e.backtrace.join "\n"
118
110
  end
119
-
120
- def kill
121
- disconnect
122
- super
111
+ end
112
+
113
+ def notify_message message
114
+ @notification.update :summary => "#{id}: #{message}"
115
+ Catfriend.whisper @notification.summary
116
+ end
117
+
118
+ def stopping?
119
+ stopped? or @stopping
120
+ end
121
+
122
+ def kill
123
+ disconnect
124
+ super
125
+ end
126
+
127
+ def get_unseen_count
128
+ begin
129
+ # fetch raises an exception when the mailbox is empty
130
+ @imap.status(@mailbox || "INBOX", ["UNSEEN"])["UNSEEN"]
131
+ rescue => e
132
+ error "failed to get count of unseen messages"
133
+ 0
123
134
  end
124
-
125
- # Connect to the configured IMAP server and return message count.
126
- def connect
127
- args = nil
128
- if not @no_ssl
129
- if @cert_file
130
- args = { :ssl => { :ca_file => @cert_file } }
131
- else
132
- args = { :ssl => true }
133
- end
134
- end
135
- @imap = Net::IMAP.new(@host, args)
136
- @imap.login(@user, @password)
137
- @imap.select(@mailbox || "INBOX")
138
-
139
- begin
140
- # fetch raises an exception when the mailbox is empty
141
- @imap.fetch('*', 'UID').first.seqno
142
- rescue
143
- 0
144
- end
135
+ end
136
+
137
+ # Connect to the configured IMAP server and return message count.
138
+ def connect
139
+ args = nil
140
+ if not @no_ssl
141
+ if @cert_file
142
+ args = { :ssl => { :ca_file => @cert_file } }
143
+ else
144
+ args = { :ssl => true }
145
+ end
145
146
  end
146
-
147
- def reconnect
148
- notify_message "#{@message_count} [reconnecting]"
149
- new_count = connect
150
- if new_count != @message_count
151
- notify_message new_count
152
- else
153
- # todo: only if it was still open
154
- @notification.close
155
- end
156
- @message_count = new_count
147
+ @imap = Net::IMAP.new(@host, args)
148
+ @imap.login(@user, @password)
149
+ @imap.select(@mailbox || "INBOX")
150
+
151
+ get_unseen_count
152
+ end
153
+
154
+ def reconnect
155
+ notify_message "#{@message_count} [reconnecting]"
156
+ new_count = connect
157
+ if new_count != @message_count
158
+ notify_message new_count
159
+ else
160
+ # todo: only if it was still open
161
+ @notification.close
157
162
  end
163
+ @message_count = new_count
164
+ end
158
165
 
159
- def disconnect
160
- @stopping = true
161
- @imap.disconnect
162
- end
166
+ def disconnect
167
+ @stopping = true
168
+ @imap.disconnect
169
+ end
163
170
 
164
- private :connect, :reconnect, :check_loop, :run, :error, :notify_message
171
+ private :connect, :reconnect, :check_loop, :run, :error, :notify_message
165
172
 
166
- attr_writer :host, :password, :id, :user, :no_ssl, :cert_file, :mailbox
167
- attr_accessor :work_account
173
+ attr_writer :host, :password, :id, :user, :no_ssl, :cert_file, :mailbox
174
+ attr_accessor :work_account
168
175
  end
169
176
 
170
- end # end module
177
+ end
@@ -0,0 +1,27 @@
1
+ require 'net/imap'
2
+
3
+ # source:
4
+ # http://claudiofloreani.blogspot.co.uk/2012/01/monkeypatching-ruby-imap-class-to-build.html
5
+ # thank you!
6
+
7
+ module Net
8
+ class IMAP
9
+ class ResponseParser
10
+ def response
11
+ token = lookahead
12
+ case token.symbol
13
+ when T_PLUS
14
+ result = continue_req
15
+ when T_STAR
16
+ result = response_untagged
17
+ else
18
+ result = response_tagged
19
+ end
20
+ match(T_SPACE) if lookahead.symbol == T_SPACE
21
+ match(T_CRLF)
22
+ match(T_EOF)
23
+ return result
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,25 +1,25 @@
1
1
  module Catfriend
2
2
 
3
3
  class << self
4
- attr_accessor :notification_timeout, :verbose
4
+ attr_accessor :notification_timeout, :verbose
5
5
 
6
- # puts something if -v was used
7
- def whisper *args
8
- puts *args if verbose
9
- end
6
+ # puts something if -v was used
7
+ def whisper *args
8
+ puts *args if verbose
9
+ end
10
10
  end
11
11
 
12
12
  # Mixin to provide #configure which allows all instance variables with write
13
13
  # accessors declared to be set from a hash.
14
14
  module AccessorsFromHash
15
- # Call this to tranfer the hash data to corresponding attributes. Any
16
- # hash keys that do not have a corresponding write accessor in the
17
- # class are silently ignored.
18
- def configure args
19
- args.each do |opt, val|
20
- instance_variable_set("@#{opt}", val) if respond_to? "#{opt}="
21
- end
15
+ # Call this to tranfer the hash data to corresponding attributes. Any
16
+ # hash keys that do not have a corresponding write accessor in the
17
+ # class are silently ignored.
18
+ def configure args
19
+ args.each do |opt, val|
20
+ instance_variable_set("@#{opt}", val) if respond_to? "#{opt}="
22
21
  end
22
+ end
23
23
  end
24
24
 
25
25
  # This class is used to signal the user made an error in their configuration.
@@ -2,28 +2,28 @@ module Catfriend
2
2
 
3
3
  # Mixin this module and define "run" for a simple runnable/joinable thread
4
4
  module Thread
5
- # Call to start a thread running via the start method.
6
- def start ; @thread = ::Thread.new { run } ; end
5
+ # Call to start a thread running via the start method.
6
+ def start ; @thread = ::Thread.new { run } ; end
7
7
 
8
- # Test whether thread is currently stopped or closing down.
9
- def stopped? ; @thread.nil? ; end
8
+ # Test whether thread is currently stopped or closing down.
9
+ def stopped? ; @thread.nil? ; end
10
10
 
11
- # Join thread if it has started.
12
- def join
13
- unless stopped?
14
- @thread.join
15
- @thread = nil
16
- end
11
+ # Join thread if it has started.
12
+ def join
13
+ unless stopped?
14
+ @thread.join
15
+ @thread = nil
17
16
  end
17
+ end
18
18
 
19
19
 
20
- # Kill thread if it has started.
21
- def kill
22
- unless stopped?
23
- @thread.kill
24
- @thread = nil
25
- end
20
+ # Kill thread if it has started.
21
+ def kill
22
+ unless stopped?
23
+ @thread.kill
24
+ @thread = nil
26
25
  end
26
+ end
27
27
  end
28
28
 
29
29
  end # end Catfriend module
metadata CHANGED
@@ -1,38 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: catfriend
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.16'
5
- prerelease:
4
+ version: '0.17'
6
5
  platform: ruby
7
6
  authors:
8
7
  - James Pike
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-02-23 00:00:00.000000000 Z
11
+ date: 2014-03-09 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: libnotify
16
- requirement: &12342540 !ruby/object:Gem::Requirement
17
- none: false
15
+ requirement: !ruby/object:Gem::Requirement
18
16
  requirements:
19
- - - ! '>='
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
19
  version: 0.7.1
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '0.8'
22
23
  type: :runtime
23
24
  prerelease: false
24
- version_requirements: *12342540
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.7.1
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '0.8'
25
33
  - !ruby/object:Gem::Dependency
26
34
  name: ruby-dbus
27
- requirement: &12340600 !ruby/object:Gem::Requirement
28
- none: false
35
+ requirement: !ruby/object:Gem::Requirement
29
36
  requirements:
30
- - - ! '>='
37
+ - - '>='
31
38
  - !ruby/object:Gem::Version
32
39
  version: '0.7'
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ version: '0.11'
33
43
  type: :runtime
34
44
  prerelease: false
35
- version_requirements: *12340600
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0.7'
50
+ - - ~>
51
+ - !ruby/object:Gem::Version
52
+ version: '0.11'
36
53
  description: E-mail checker with libnotify desktop notifications.
37
54
  email:
38
55
  - catfriend@chilon.net
@@ -43,36 +60,35 @@ extra_rdoc_files: []
43
60
  files:
44
61
  - LICENSE
45
62
  - catfriend.example
46
- - lib/catfriend/dbus.rb
47
63
  - lib/catfriend/server.rb
48
- - lib/catfriend/filetokenstack.rb
64
+ - lib/catfriend/dbus.rb
49
65
  - lib/catfriend/thread.rb
66
+ - lib/catfriend/net_imap_exchange_patch.rb
50
67
  - lib/catfriend/imap.rb
51
- - !binary |-
52
- YmluL2NhdGZyaWVuZA==
68
+ - lib/catfriend/filetokenstack.rb
69
+ - bin/catfriend
53
70
  homepage: https://github.com/nuisanceofcats/catfriend
54
71
  licenses:
55
72
  - Expat
73
+ metadata: {}
56
74
  post_install_message:
57
75
  rdoc_options: []
58
76
  require_paths:
59
77
  - lib
60
78
  required_ruby_version: !ruby/object:Gem::Requirement
61
- none: false
62
79
  requirements:
63
- - - ! '>='
80
+ - - '>='
64
81
  - !ruby/object:Gem::Version
65
82
  version: '0'
66
83
  required_rubygems_version: !ruby/object:Gem::Requirement
67
- none: false
68
84
  requirements:
69
- - - ! '>='
85
+ - - '>='
70
86
  - !ruby/object:Gem::Version
71
87
  version: '1.3'
72
88
  requirements: []
73
89
  rubyforge_project:
74
- rubygems_version: 1.8.11
90
+ rubygems_version: 2.0.14
75
91
  signing_key:
76
- specification_version: 3
92
+ specification_version: 4
77
93
  summary: E-mail checker with desktop notifications.
78
94
  test_files: []